diff --git a/modules/openapi-generator/src/main/resources/ruby-client/api.mustache b/modules/openapi-generator/src/main/resources/ruby-client/api.mustache index 97f38c704126..76b19e26df94 100644 --- a/modules/openapi-generator/src/main/resources/ruby-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/ruby-client/api.mustache @@ -180,7 +180,23 @@ module {{moduleName}} query_params[:'{{{baseName}}}'] = {{{paramName}}}.to_json {{/queryIsJsonMimeType}} {{^queryIsJsonMimeType}} + {{#isMap}} + {{#isExplode}} + {{^isDeepObject}} + # form style explodes an object into one query parameter per entry, keyed by the property name alone + {{{paramName}}}.each { |name, value| query_params[name.to_s] = value } + {{/isDeepObject}} + {{#isDeepObject}} + query_params[:'{{{baseName}}}'] = {{{paramName}}} + {{/isDeepObject}} + {{/isExplode}} + {{^isExplode}} + query_params[:'{{{baseName}}}'] = {{{paramName}}} + {{/isExplode}} + {{/isMap}} + {{^isMap}} query_params[:'{{{baseName}}}'] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}{{/collectionFormat}} + {{/isMap}} {{/queryIsJsonMimeType}} {{/required}} {{/queryParams}} @@ -190,7 +206,23 @@ module {{moduleName}} query_params[:'{{{baseName}}}'] = opts[:'{{{paramName}}}'].to_json if !opts[:'{{{paramName}}}'].nil? {{/queryIsJsonMimeType}} {{^queryIsJsonMimeType}} + {{#isMap}} + {{#isExplode}} + {{^isDeepObject}} + # form style explodes an object into one query parameter per entry, keyed by the property name alone + opts[:'{{{paramName}}}'].each { |name, value| query_params[name.to_s] = value } if !opts[:'{{{paramName}}}'].nil? + {{/isDeepObject}} + {{#isDeepObject}} + query_params[:'{{{baseName}}}'] = opts[:'{{{paramName}}}'] if !opts[:'{{{paramName}}}'].nil? + {{/isDeepObject}} + {{/isExplode}} + {{^isExplode}} + query_params[:'{{{baseName}}}'] = opts[:'{{{paramName}}}'] if !opts[:'{{{paramName}}}'].nil? + {{/isExplode}} + {{/isMap}} + {{^isMap}} query_params[:'{{{baseName}}}'] = {{#collectionFormat}}@api_client.build_collection_param(opts[:'{{{paramName}}}'], :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}opts[:'{{{paramName}}}']{{/collectionFormat}} if !opts[:'{{{paramName}}}'].nil? + {{/isMap}} {{/queryIsJsonMimeType}} {{/required}} {{/queryParams}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java index ed256377bc80..a98d034b968a 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java @@ -71,6 +71,42 @@ public void testGenerateRubyClientWithHtmlEntity() throws Exception { } } + @Test(description = "Verify an object query parameter is exploded, whether or not it declares its properties") + public void testExplodedObjectQueryParameter() throws Exception { + final File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/exploded-object-query-param.yaml"); + RubyClientCodegen codegen = new RubyClientCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + + ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegen); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + files.forEach(File::deleteOnExit); + + File apiFile = files.stream() + .filter(f -> f.getName().equals("default_api.rb")) + .findFirst() + .orElseThrow(() -> new AssertionError("default_api.rb not found in generated files")); + + // form style with explode - the default - puts every entry on the wire under its own + // property name. Assigning the whole hash under the parameter name left the http + // library to serialize it in bracket style, which is what used to happen. + TestUtils.assertFileContains(apiFile.toPath(), + "opts[:'filter'].each { |name, value| query_params[name.to_s] = value } if !opts[:'filter'].nil?"); + TestUtils.assertFileNotContains(apiFile.toPath(), "query_params[:'filter']"); + + // a declared map behaves the same way + TestUtils.assertFileContains(apiFile.toPath(), + "opts[:'typed_filter'].each { |name, value| query_params[name.to_s] = value } if !opts[:'typed_filter'].nil?"); + + // deepObject and form without explode both keep a single parameter + TestUtils.assertFileContains(apiFile.toPath(), + "query_params[:'deepFilter'] = opts[:'deep_filter'] if !opts[:'deep_filter'].nil?", + "query_params[:'flatFilter'] = opts[:'flat_filter'] if !opts[:'flat_filter'].nil?"); + } + @Test public void testInitialConfigValues() { final RubyClientCodegen codegen = new RubyClientCodegen(); diff --git a/modules/openapi-generator/src/test/resources/3_0/exploded-object-query-param.yaml b/modules/openapi-generator/src/test/resources/3_0/exploded-object-query-param.yaml new file mode 100644 index 000000000000..fd6fce7d5807 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/exploded-object-query-param.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.3 +info: + title: Exploded object query parameters + description: > + Object typed query parameters, covering the four combinations of style and explode that + decide how an object is put on the wire. The free-form variants matter because a + free-form object is flagged isMap but not isContainer. + version: 1.0.0 +servers: + - url: localhost:8080 +paths: + /items: + get: + operationId: listItems + parameters: + # style and explode both left out, so the form/true defaults apply: every entry + # becomes its own parameter, keyed by the property name alone. + - in: query + name: filter + schema: + type: object + # the same, but declared as a map rather than as a free-form object + - in: query + name: typedFilter + schema: + type: object + additionalProperties: + type: string + # deepObject nests each entry under the parameter name: deepFilter[key]=value + - in: query + name: deepFilter + style: deepObject + explode: true + schema: + type: object + # form without explode keeps a single parameter carrying the whole object + - in: query + name: flatFilter + style: form + explode: false + schema: + type: object + responses: + '200': + description: a list of items + content: + application/json: + schema: + type: array + items: + type: string diff --git a/samples/client/petstore/ruby-autoload/lib/petstore/api/fake_api.rb b/samples/client/petstore/ruby-autoload/lib/petstore/api/fake_api.rb index a46c90375e88..90ae492d58e5 100644 --- a/samples/client/petstore/ruby-autoload/lib/petstore/api/fake_api.rb +++ b/samples/client/petstore/ruby-autoload/lib/petstore/api/fake_api.rb @@ -1556,7 +1556,8 @@ def test_query_parameter_collection_format_with_http_info(pipe, ioutil, http, ur query_params[:'url'] = @api_client.build_collection_param(url, :csv) query_params[:'context'] = @api_client.build_collection_param(context, :multi) query_params[:'allowEmpty'] = allow_empty - query_params[:'language'] = opts[:'language'] if !opts[:'language'].nil? + # form style explodes an object into one query parameter per entry, keyed by the property name alone + opts[:'language'].each { |name, value| query_params[name.to_s] = value } if !opts[:'language'].nil? # header parameters header_params = opts[:header_params] || {} diff --git a/samples/client/petstore/ruby-faraday/lib/petstore/api/fake_api.rb b/samples/client/petstore/ruby-faraday/lib/petstore/api/fake_api.rb index 7efdb0244dea..c4fd19700a59 100644 --- a/samples/client/petstore/ruby-faraday/lib/petstore/api/fake_api.rb +++ b/samples/client/petstore/ruby-faraday/lib/petstore/api/fake_api.rb @@ -1571,7 +1571,8 @@ def test_query_parameter_collection_format_with_http_info(pipe, ioutil, http, ur query_params[:'url'] = @api_client.build_collection_param(url, :csv) query_params[:'context'] = @api_client.build_collection_param(context, :multi) query_params[:'allowEmpty'] = allow_empty - query_params[:'language'] = opts[:'language'] if !opts[:'language'].nil? + # form style explodes an object into one query parameter per entry, keyed by the property name alone + opts[:'language'].each { |name, value| query_params[name.to_s] = value } if !opts[:'language'].nil? # header parameters header_params = opts[:header_params] || {} diff --git a/samples/client/petstore/ruby-httpx/lib/petstore/api/fake_api.rb b/samples/client/petstore/ruby-httpx/lib/petstore/api/fake_api.rb index 7efdb0244dea..c4fd19700a59 100644 --- a/samples/client/petstore/ruby-httpx/lib/petstore/api/fake_api.rb +++ b/samples/client/petstore/ruby-httpx/lib/petstore/api/fake_api.rb @@ -1571,7 +1571,8 @@ def test_query_parameter_collection_format_with_http_info(pipe, ioutil, http, ur query_params[:'url'] = @api_client.build_collection_param(url, :csv) query_params[:'context'] = @api_client.build_collection_param(context, :multi) query_params[:'allowEmpty'] = allow_empty - query_params[:'language'] = opts[:'language'] if !opts[:'language'].nil? + # form style explodes an object into one query parameter per entry, keyed by the property name alone + opts[:'language'].each { |name, value| query_params[name.to_s] = value } if !opts[:'language'].nil? # header parameters header_params = opts[:header_params] || {} diff --git a/samples/client/petstore/ruby/lib/petstore/api/fake_api.rb b/samples/client/petstore/ruby/lib/petstore/api/fake_api.rb index 09633923caf0..28dec0f0e2fa 100644 --- a/samples/client/petstore/ruby/lib/petstore/api/fake_api.rb +++ b/samples/client/petstore/ruby/lib/petstore/api/fake_api.rb @@ -1571,7 +1571,8 @@ def test_query_parameter_collection_format_with_http_info(pipe, ioutil, http, ur query_params[:'url'] = @api_client.build_collection_param(url, :csv) query_params[:'context'] = @api_client.build_collection_param(context, :multi) query_params[:'allowEmpty'] = allow_empty - query_params[:'language'] = opts[:'language'] if !opts[:'language'].nil? + # form style explodes an object into one query parameter per entry, keyed by the property name alone + opts[:'language'].each { |name, value| query_params[name.to_s] = value } if !opts[:'language'].nil? # header parameters header_params = opts[:header_params] || {}