diff --git a/src/core/components/parameter-include-empty.jsx b/src/core/components/parameter-include-empty.jsx new file mode 100644 index 00000000..9e90df30 --- /dev/null +++ b/src/core/components/parameter-include-empty.jsx @@ -0,0 +1,27 @@ +import React from "react" +import cx from "classnames" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export const ParameterIncludeEmpty = ({ param, isIncluded, onChange, isDisabled }) => { + const onCheckboxChange = e => { + onChange(e.target.checked) + } + if(!param.get("allowEmptyValue")) { + return null + } + return
+ + Send empty value +
+} +ParameterIncludeEmpty.propTypes = { + param: ImPropTypes.map.isRequired, + isIncluded: PropTypes.bool.isRequired, + isDisabled: PropTypes.bool.isRequired, + onChange: PropTypes.func.isRequired, +} + +export default ParameterIncludeEmpty diff --git a/src/core/components/parameter-row.jsx b/src/core/components/parameter-row.jsx index a756a4ce..73f490ae 100644 --- a/src/core/components/parameter-row.jsx +++ b/src/core/components/parameter-row.jsx @@ -15,6 +15,7 @@ export default class ParameterRow extends Component { isExecute: PropTypes.bool, onChangeConsumes: PropTypes.func.isRequired, specSelectors: PropTypes.object.isRequired, + specActions: PropTypes.object.isRequired, pathMethod: PropTypes.array.isRequired, getConfigs: PropTypes.func.isRequired, specPath: ImPropTypes.list.isRequired @@ -61,7 +62,23 @@ export default class ParameterRow extends Component { onChangeWrapper = (value, isXml = false) => { let { onChange, rawParam } = this.props - return onChange(rawParam, value, isXml) + let valueForUpstream + + // Coerce empty strings and empty Immutable objects to null + if(value === "" || (value && value.size === 0)) { + valueForUpstream = null + } else { + valueForUpstream = value + } + + return onChange(rawParam, valueForUpstream, isXml) + } + + onChangeIncludeEmpty = (newValue) => { + let { specActions, param, pathMethod } = this.props + const paramName = param.get("name") + const paramIn = param.get("in") + return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue) } setDefaultValue = () => { @@ -120,6 +137,7 @@ export default class ParameterRow extends Component { const ModelExample = getComponent("modelExample") const Markdown = getComponent("Markdown") const ParameterExt = getComponent("ParameterExt") + const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty") let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) let format = param.get("format") @@ -225,6 +243,16 @@ export default class ParameterRow extends Component { : null } + { + !bodyParam && isExecute ? + + : null + } + diff --git a/src/core/components/parameters.jsx b/src/core/components/parameters.jsx index e5ce323b..5245cc12 100644 --- a/src/core/components/parameters.jsx +++ b/src/core/components/parameters.jsx @@ -65,7 +65,8 @@ export default class Parameters extends Component { fn, getComponent, getConfigs, - specSelectors, + specSelectors, + specActions, pathMethod } = this.props @@ -107,6 +108,7 @@ export default class Parameters extends Component { onChange={ this.onChange } onChangeConsumes={this.onChangeConsumesWrapper} specSelectors={ specSelectors } + specActions={specActions} pathMethod={ pathMethod } isExecute={ isExecute }/> )).toArray() diff --git a/src/core/plugins/oas3/wrap-components/parameters.jsx b/src/core/plugins/oas3/wrap-components/parameters.jsx index 220b02af..a9df082c 100644 --- a/src/core/plugins/oas3/wrap-components/parameters.jsx +++ b/src/core/plugins/oas3/wrap-components/parameters.jsx @@ -90,6 +90,7 @@ class Parameters extends Component { getComponent, getConfigs, specSelectors, + specActions, oas3Actions, oas3Selectors, pathMethod, @@ -151,6 +152,7 @@ class Parameters extends Component { onChange={ this.onChange } onChangeConsumes={this.onChangeConsumesWrapper} specSelectors={ specSelectors } + specActions={ specActions } pathMethod={ pathMethod } isExecute={ isExecute }/> )).toArray() diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js index 9099089f..980ba284 100644 --- a/src/core/plugins/spec/actions.js +++ b/src/core/plugins/spec/actions.js @@ -14,6 +14,7 @@ export const UPDATE_SPEC = "spec_update_spec" export const UPDATE_URL = "spec_update_url" export const UPDATE_JSON = "spec_update_json" export const UPDATE_PARAM = "spec_update_param" +export const UPDATE_EMPTY_PARAM_INCLUSION = "spec_update_empty_param_inclusion" export const VALIDATE_PARAMS = "spec_validate_param" export const SET_RESPONSE = "spec_set_response" export const SET_REQUEST = "spec_set_request" @@ -270,6 +271,18 @@ export const validateParams = ( payload, isOAS3 ) =>{ } } +export const updateEmptyParamInclusion = ( pathMethod, paramName, paramIn, includeEmptyValue ) =>{ + return { + type: UPDATE_EMPTY_PARAM_INCLUSION, + payload:{ + pathMethod, + paramName, + paramIn, + includeEmptyValue + } + } +} + export function clearValidateParams( payload ){ return { type: CLEAR_VALIDATE_PARAMS, @@ -327,7 +340,28 @@ export const executeRequest = (req) => let { pathName, method, operation } = req let { requestInterceptor, responseInterceptor } = getConfigs() + let op = operation.toJS() + + // ensure that explicitly-included params are in the request + + if(op && op.parameters && op.parameters.length) { + op.parameters + .filter(param => param && param.allowEmptyValue === true) + .forEach(param => { + if (specSelectors.parameterInclusionSettingFor([pathName, method], param.name, param.in)) { + req.parameters = req.parameters || {} + const paramValue = req.parameters[param.name] + + // if the value is falsy or an empty Immutable iterable... + if(!paramValue || (paramValue && paramValue.size === 0)) { + // set it to empty string, so Swagger Client will treat it as + // present but empty. + req.parameters[param.name] = "" + } + } + }) + } // if url is relative, parseUrl makes it absolute by inferring from `window.location` req.contextUrl = parseUrl(specSelectors.url()).toString() diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js index a510bb50..ef0424b7 100644 --- a/src/core/plugins/spec/reducers.js +++ b/src/core/plugins/spec/reducers.js @@ -12,6 +12,7 @@ import { UPDATE_URL, UPDATE_JSON, UPDATE_PARAM, + UPDATE_EMPTY_PARAM_INCLUSION, VALIDATE_PARAMS, SET_RESPONSE, SET_REQUEST, @@ -70,6 +71,22 @@ export default { ) }, + [UPDATE_EMPTY_PARAM_INCLUSION]: ( state, {payload} ) => { + let { pathMethod, paramName, paramIn, includeEmptyValue } = payload + + if(!paramName || !paramIn) { + console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey.") + return state + } + + const paramKey = `${paramName}.${paramIn}` + + return state.setIn( + ["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], + includeEmptyValue + ) + }, + [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => { let meta = state.getIn( [ "meta", "paths", ...pathMethod ], fromJS({}) ) let isXml = /xml/i.test(meta.get("consumes_value")) diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 432d9698..cfc6b5fb 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -311,6 +311,11 @@ export const parameterWithMetaByIdentity = (state, pathMethod, param) => { return mergedParams.find(curr => curr.get("in") === param.get("in") && curr.get("name") === param.get("name"), OrderedMap()) } +export const parameterInclusionSettingFor = (state, pathMethod, paramName, paramIn) => { + const paramKey = `${paramName}.${paramIn}` + return state.getIn(["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], false) +} + export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => { const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap()) diff --git a/src/core/presets/base.js b/src/core/presets/base.js index 8165f1b3..50663213 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -42,6 +42,7 @@ import Response from "core/components/response" import ResponseBody from "core/components/response-body" import Parameters from "core/components/parameters" import ParameterExt from "core/components/parameter-extension" +import ParameterIncludeEmpty from "core/components/parameter-include-empty" import ParameterRow from "core/components/parameter-row" import Execute from "core/components/execute" import Headers from "core/components/headers" @@ -143,6 +144,7 @@ export default function() { OperationExt, OperationExtRow, ParameterExt, + ParameterIncludeEmpty, OperationTag, OperationContainer, DeepLink, diff --git a/src/style/_table.scss b/src/style/_table.scss index 45e0748c..95414542 100644 --- a/src/style/_table.scss +++ b/src/style/_table.scss @@ -139,6 +139,20 @@ table @include text_code($table-parameter-deprecated-font-color); } +.parameter__empty_value_toggle { + font-size: 13px; + padding-top: 5px; + padding-bottom: 12px; + + input { + margin-right: 7px; + } + + &.disabled { + opacity: 0.7; + } +} + .table-container { diff --git a/test/core/plugins/spec/actions.js b/test/core/plugins/spec/actions.js index 47dd7955..235aabba 100644 --- a/test/core/plugins/spec/actions.js +++ b/test/core/plugins/spec/actions.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ import expect, { createSpy } from "expect" import { fromJS } from "immutable" -import { execute, executeRequest, changeParamByIdentity } from "corePlugins/spec/actions" +import { execute, executeRequest, changeParamByIdentity, updateEmptyParamInclusion } from "corePlugins/spec/actions" describe("spec plugin - actions", function(){ @@ -207,4 +207,22 @@ describe("spec plugin - actions", function(){ }) }) }) + + describe("updateEmptyParamInclusion", function () { + it("should map its arguments to a payload", function () { + const pathMethod = ["/one", "get"] + + const result = updateEmptyParamInclusion(pathMethod, "param", "query", true) + + expect(result).toEqual({ + type: "spec_update_empty_param_inclusion", + payload: { + pathMethod, + paramName: "param", + paramIn: "query", + includeEmptyValue: true + } + }) + }) + }) }) diff --git a/test/core/plugins/spec/reducer.js b/test/core/plugins/spec/reducer.js index 89de197a..643d249f 100644 --- a/test/core/plugins/spec/reducer.js +++ b/test/core/plugins/spec/reducer.js @@ -178,4 +178,26 @@ describe("spec plugin - reducer", function(){ expect(value).toEqual(`{ "a": 123 }`) }) }) + describe("SPEC_UPDATE_EMPTY_PARAM_INCLUSION", function() { + it("should store parameter values by name+in", () => { + const updateParam = reducer["spec_update_empty_param_inclusion"] + + const path = "/pet/post" + const method = "POST" + + const state = fromJS({}) + + const result = updateParam(state, { + payload: { + pathMethod: [path, method], + paramName: "param", + paramIn: "query", + includeEmptyValue: true + } + }) + + const response = result.getIn(["meta", "paths", path, method, "parameter_inclusions", "param.query"]) + expect(response).toEqual(true) + }) + }) }) diff --git a/test/core/plugins/spec/selectors.js b/test/core/plugins/spec/selectors.js index 39483171..412407b2 100644 --- a/test/core/plugins/spec/selectors.js +++ b/test/core/plugins/spec/selectors.js @@ -14,7 +14,8 @@ import Petstore from "./assets/petstore.json" import { operationWithMeta, parameterWithMeta, - parameterWithMetaByIdentity + parameterWithMetaByIdentity, + parameterInclusionSettingFor } from "../../../../src/core/plugins/spec/selectors" describe("spec plugin - selectors", function(){ @@ -712,4 +713,42 @@ describe("spec plugin - selectors", function(){ }) }) }) + describe("parameterInclusionSettingFor", function() { + it("should support getting name+in param inclusion settings", function () { + const param = fromJS({ + name: "param", + in: "query", + allowEmptyValue: true + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + param + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + "parameter_inclusions": { + [`param.query`]: true + } + } + } + } + } + }) + + const result = parameterInclusionSettingFor(state, ["/", "get"], "param", "query") + + expect(result).toEqual(true) + }) + }) }) diff --git a/test/e2e/scenarios/bugs/4587.js b/test/e2e/scenarios/bugs/4587.js new file mode 100644 index 00000000..39e5b161 --- /dev/null +++ b/test/e2e/scenarios/bugs/4587.js @@ -0,0 +1,36 @@ +describe("bug #4587: clearing header param values", function () { + let mainPage + beforeEach(function (client, done) { + mainPage = client + .url("localhost:3230") + .page.main() + + client.waitForElementVisible(".download-url-input:not([disabled])", 5000) + .pause(2000) + .clearValue(".download-url-input") + .setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4587.yaml") + .click("button.download-url-button") + .pause(1000) + + done() + }) + + afterEach(function (client, done) { + done() + }) + + it("sets a required initial value based the first enum value", function (client) { + client.waitForElementVisible(".opblock-tag-section", 10000) + .click("#operations-sql-execSql") + .waitForElementVisible(".opblock.is-open", 5000) + .click("button.btn.try-out__btn") + .setValue(`tr[data-param-name="x-irest-conn"] input`, "hi") + .click("button.btn.execute") + .waitForElementVisible(".request-url", 2000) + .setValue(`tr[data-param-name="x-irest-conn"] input`, `\u0008\u0008\u0008`) // backspaces + .pause(900) + .click("button.btn.execute") + .expect.element("textarea.curl").text + .to.not.contain(`x-irest-conn`) + }) +}) diff --git a/test/e2e/scenarios/features/allow-empty-value.openapi.js b/test/e2e/scenarios/features/allow-empty-value.openapi.js new file mode 100644 index 00000000..77611f58 --- /dev/null +++ b/test/e2e/scenarios/features/allow-empty-value.openapi.js @@ -0,0 +1,433 @@ +describe("feature: OpenAPI 3 allowEmptyValue", function () { + beforeEach(function (client, done) { + client + .url("localhost:3230") + .page.main() + + client.waitForElementVisible(".download-url-input:not([disabled])", 5000) + .clearValue(".download-url-input") + .setValue(".download-url-input", "/test-specs/features/allow-empty-value.openapi.yaml") + .click("button.download-url-button") + .waitForElementVisible(".opblock", 10000) + + done() + }) + + afterEach(function (client, done) { + done() + }) + + describe("regular parameters", function () { + it("should set and unset an integer value", function (client) { + const inputSelector = `tr[data-param-name="int"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a string value", function (client) { + const inputSelector = `tr[data-param-name="str"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a number value", function (client) { + const inputSelector = `tr[data-param-name="num"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a boolean value", function (client) { + const inputSelector = `tr[data-param-name="bool"] select` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} [value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} [value=""]`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset an array value", function (client) { + const inputSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} .json-schema-form-item-add`) + .setValue(`${inputSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} .json-schema-form-item-remove`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + }) + + describe("allowEmptyValue parameters", function () { + describe("normal behavior", function () { + it("should set and unset an integer value", function (client) { + const inputSelector = `tr[data-param-name="int"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a string value", function (client) { + const inputSelector = `tr[data-param-name="str"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a number value", function (client) { + const inputSelector = `tr[data-param-name="num"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a boolean value", function (client) { + const inputSelector = `tr[data-param-name="bool"] select` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} [value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} [value=""]`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset an array value", function (client) { + const inputSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} .json-schema-form-item-add`) + .setValue(`${inputSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} .json-schema-form-item-remove`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + }) + describe("send empty inital value behavior", function () { + it("should send an empty integer value", function (client) { + const paramSelector = `tr[data-param-name="int"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`) + }) + it("should send an empty string value", function (client) { + const paramSelector = `tr[data-param-name="str"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`) + }) + it("should send an empty number value", function (client) { + const paramSelector = `tr[data-param-name="num"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`) + }) + it("should send an empty boolean value", function (client) { + const paramSelector = `tr[data-param-name="bool"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`) + }) + it("should send an empty array value", function (client) { + const paramSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`) + }) + }) + describe("modify and send empty behavior", function () { + it("should set, unset and send an empty integer value", function (client) { + const paramSelector = `tr[data-param-name="int"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`) + }) + it("should set, unset and send an empty string value", function (client) { + const paramSelector = `tr[data-param-name="str"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`) + }) + it("should set, unset and send an empty number value", function (client) { + const paramSelector = `tr[data-param-name="num"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`) + }) + it("should set, unset and send an empty boolean value", function (client) { + const paramSelector = `tr[data-param-name="bool"]` + const inputSelector = `${paramSelector} select` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} option[value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .click(`${inputSelector} option[value=""]`) + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`) + }) + it("should set, unset and send an empty array value", function (client) { + const paramSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${paramSelector} .json-schema-form-item-add`) + .setValue(`${paramSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${paramSelector} .json-schema-form-item-remove`) + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`) + }) + }) + }) +}) diff --git a/test/e2e/scenarios/features/allow-empty-value.swagger.js b/test/e2e/scenarios/features/allow-empty-value.swagger.js new file mode 100644 index 00000000..bb118bac --- /dev/null +++ b/test/e2e/scenarios/features/allow-empty-value.swagger.js @@ -0,0 +1,433 @@ +describe("feature: Swagger 2 allowEmptyValue", function () { + beforeEach(function (client, done) { + client + .url("localhost:3230") + .page.main() + + client.waitForElementVisible(".download-url-input:not([disabled])", 5000) + .clearValue(".download-url-input") + .setValue(".download-url-input", "/test-specs/features/allow-empty-value.swagger.yaml") + .click("button.download-url-button") + .waitForElementVisible(".opblock", 10000) + + done() + }) + + afterEach(function (client, done) { + done() + }) + + describe("regular parameters", function () { + it("should set and unset an integer value", function (client) { + const inputSelector = `tr[data-param-name="int"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a string value", function (client) { + const inputSelector = `tr[data-param-name="str"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a number value", function (client) { + const inputSelector = `tr[data-param-name="num"] input` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset a boolean value", function (client) { + const inputSelector = `tr[data-param-name="bool"] select` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} [value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} [value=""]`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + it("should set and unset an array value", function (client) { + const inputSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_regularParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} .json-schema-form-item-add`) + .setValue(`${inputSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} .json-schema-form-item-remove`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/regularParams"`) + }) + }) + + describe("allowEmptyValue parameters", function () { + describe("normal behavior", function () { + it("should set and unset an integer value", function (client) { + const inputSelector = `tr[data-param-name="int"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a string value", function (client) { + const inputSelector = `tr[data-param-name="str"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a number value", function (client) { + const inputSelector = `tr[data-param-name="num"] input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset a boolean value", function (client) { + const inputSelector = `tr[data-param-name="bool"] select` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} [value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} [value=""]`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + it("should set and unset an array value", function (client) { + const inputSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} .json-schema-form-item-add`) + .setValue(`${inputSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${inputSelector} .json-schema-form-item-remove`) + .pause(200) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams"`) + }) + }) + describe("send empty inital value behavior", function () { + it("should send an empty integer value", function (client) { + const paramSelector = `tr[data-param-name="int"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`) + }) + it("should send an empty string value", function (client) { + const paramSelector = `tr[data-param-name="str"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`) + }) + it("should send an empty number value", function (client) { + const paramSelector = `tr[data-param-name="num"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`) + }) + it("should send an empty boolean value", function (client) { + const paramSelector = `tr[data-param-name="bool"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`) + }) + it("should send an empty array value", function (client) { + const paramSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // tick "send empty value" box and execute + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`) + }) + }) + describe("modify and send empty behavior", function () { + it("should set, unset and send an empty integer value", function (client) { + const paramSelector = `tr[data-param-name="int"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`) + }) + it("should set, unset and send an empty string value", function (client) { + const paramSelector = `tr[data-param-name="str"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`) + }) + it("should set, unset and send an empty number value", function (client) { + const paramSelector = `tr[data-param-name="num"]` + const inputSelector = `${paramSelector} input` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .setValue(inputSelector, "123") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`) + }) + it("should set, unset and send an empty boolean value", function (client) { + const paramSelector = `tr[data-param-name="bool"]` + const inputSelector = `${paramSelector} select` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${inputSelector} option[value="true"]`) + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, click "send empty", execute again, assert + .click(`${inputSelector} option[value=""]`) + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`) + }) + it("should set, unset and send an empty array value", function (client) { + const paramSelector = `tr[data-param-name="arr"]` + + client // open try-it-out + .click("#operations-default-get_emptyValueParams") + .waitForElementVisible("button.btn.try-out__btn", 5000) + .click("button.btn.try-out__btn") + .pause(200) + + client // set parameter, to ensure an initial value is set + .click(`${paramSelector} .json-schema-form-item-add`) + .setValue(`${paramSelector} input`, "asdf") + .click("button.btn.execute.opblock-control__btn") + .pause(200) + + client // remove initial value, execute again + .click(`${paramSelector} .json-schema-form-item-remove`) + .pause(400) + .click(`${paramSelector} .parameter__empty_value_toggle input`) + .click("button.btn.execute.opblock-control__btn") + .expect.element("textarea.curl").text + .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`) + }) + }) + }) +}) diff --git a/test/e2e/specs/bugs/4587.yaml b/test/e2e/specs/bugs/4587.yaml new file mode 100644 index 00000000..f8e08217 --- /dev/null +++ b/test/e2e/specs/bugs/4587.yaml @@ -0,0 +1,126 @@ +--- +swagger: '2.0' +info: + version: 0.0.1 + title: DDErl REST interface + description: RESTful access to IMEM DB and DDErl +schemes: +- http +- https +securityDefinitions: + basicAuth: + type: basic + description: HTTP Basic Authentication Username:Password +basePath: "/dderlrest/0.0.1" +paths: + "/sql/": + get: + tags: + - sql + security: + - basicAuth: [] + summary: execute sql + operationId: execSql + description: Prepare and execute SQL statements + parameters: + - name: x-irest-conn + in: header + required: false + description: ErlImem connection identifier + type: string + produces: + - application/json + responses: + '200': + description: OK + schema: + type: object + headers: + x-irest-conn: + description: ErlImem connection identifier + type: string + '403': + description: Malformed/Invalid + schema: + "$ref": "#/definitions/ErrorResponse" +definitions: + ErrorResponse: + readOnly: true + type: object + required: + - errorCode + - errorMessage + - errorDetails + properties: + errorCode: + description: Error Code + type: number + example: 1400 + errorMessage: + description: Error Message + type: string + example: malformed + errorDetails: + description: Error Details + type: string + example: mandatory properties missing or bad type + ViewParams: + readOnly: true + type: array + items: + type: object + required: + - typ + - value + - name + properties: + name: + description: Name + type: string + example: ":atom_user" + value: + description: Value + type: string + example: system + typ: + description: Datatype + type: string + enum: + - atom + - binary + - raw + - blob + - rowid + - binstr + - clob + - nclob + - varchar2 + - nvarchar2 + - char + - nchar + - boolean + - datetime + - decimal + - float + - fun + - integer + - ipaddr + - list + - map + - number + - pid + - ref + - string + - term + - binterm + - timestamp + - tuple + - userid + example: atom + dir: + description: Direction + type: string + enum: + - in + - out + default: in diff --git a/test/e2e/specs/features/allow-empty-value.openapi.yaml b/test/e2e/specs/features/allow-empty-value.openapi.yaml new file mode 100644 index 00000000..7190097c --- /dev/null +++ b/test/e2e/specs/features/allow-empty-value.openapi.yaml @@ -0,0 +1,64 @@ +openapi: "3.0.0" + +paths: + /regularParams: + get: + parameters: + - name: int + in: query + schema: + type: integer + - name: str + in: query + schema: + type: string + - name: num + in: query + schema: + type: number + - name: bool + in: query + schema: + type: boolean + - name: arr + in: query + schema: + type: array + items: + type: string + responses: + 200: + description: ok + /emptyValueParams: + get: + parameters: + - name: int + in: query + schema: + type: integer + allowEmptyValue: true + - name: str + in: query + schema: + type: string + allowEmptyValue: true + - name: num + in: query + schema: + type: number + allowEmptyValue: true + - name: bool + in: query + schema: + type: boolean + allowEmptyValue: true + - name: arr + in: query + schema: + type: array + items: + type: string + allowEmptyValue: true + responses: + 200: + description: ok \ No newline at end of file diff --git a/test/e2e/specs/features/allow-empty-value.swagger.yaml b/test/e2e/specs/features/allow-empty-value.swagger.yaml new file mode 100644 index 00000000..9075987c --- /dev/null +++ b/test/e2e/specs/features/allow-empty-value.swagger.yaml @@ -0,0 +1,57 @@ +swagger: "2.0" + +consumes: +- application/json +- multipart/form-data +paths: + /regularParams: + get: + parameters: + - name: int + in: query + type: integer + - name: str + in: query + type: string + - name: num + in: query + type: number + - name: bool + in: query + type: boolean + - name: arr + in: query + type: array + items: + type: string + responses: + 200: + description: ok + /emptyValueParams: + get: + parameters: + - name: int + in: query + type: integer + allowEmptyValue: true + - name: str + in: query + type: string + allowEmptyValue: true + - name: num + in: query + type: number + allowEmptyValue: true + - name: bool + in: query + type: boolean + allowEmptyValue: true + - name: arr + in: query + type: array + items: + type: string + allowEmptyValue: true + responses: + 200: + description: ok \ No newline at end of file