diff --git a/modules/openapi-generator/src/main/resources/go/client.mustache b/modules/openapi-generator/src/main/resources/go/client.mustache index c0fea7cafc3d..41b4b5862a73 100644 --- a/modules/openapi-generator/src/main/resources/go/client.mustache +++ b/modules/openapi-generator/src/main/resources/go/client.mustache @@ -191,10 +191,16 @@ func parameterAddToHeaderOrQuery(headerOrQueryParams interface{}, keyPrefix stri for i:=0;i files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + // The style reaches the runtime, which decides between an exploded entry and a + // bracketed one. Before, every map was bracketed whatever the style said. + TestUtils.assertFileContains(Paths.get(output + "/client.go"), + "var keyPrefixForMapEntry = fmt.Sprintf(\"%s[%s]\", keyPrefix, k.String())", + "if style == \"form\" {", + "keyPrefixForMapEntry = k.String()", + "styleForMapEntry = \"\"", + "parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefixForMapEntry, v.Interface(), styleForMapEntry, collectionType)", + // an array element does not inherit the form flattening: a map nested one + // level down keeps its accumulated path instead of being keyed by its + // property names alone + "var styleForElement = style", + "} else if style == \"form\" {", + "styleForElement = \"\"", + "parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefixForCollectionType, arrayValue.Interface(), styleForElement, collectionType)"); + + // and the api hands the declared style over. Note that explode is not passed, so a + // form style object is treated as exploded whether or not it says explode: false - + // that combination was bracketed before this change and is exploded after it, both + // of which differ from the comma joined pairs the specification asks for. + Path api = Paths.get(output + "/api_default.go"); + TestUtils.assertFileContains(api, + "parameterAddToHeaderOrQuery(localVarQueryParams, \"filter\", r.filter, \"form\", \"\")", + "parameterAddToHeaderOrQuery(localVarQueryParams, \"typedFilter\", r.typedFilter, \"form\", \"\")", + "parameterAddToHeaderOrQuery(localVarQueryParams, \"deepFilter\", r.deepFilter, \"deepObject\", \"\")", + "parameterAddToHeaderOrQuery(localVarQueryParams, \"flatFilter\", r.flatFilter, \"form\", \"\")", + // the array of objects the element reset exists for: not exploded, so the + // whole slice reaches the runtime as one form style value and enters the + // array branch that must not pass the flattening down to its elements + "parameterAddToHeaderOrQuery(localVarQueryParams, \"arrayFilter\", r.arrayFilter, \"form\", \"csv\")"); + } + @Test public void testNullableComposition() throws IOException { File output = Files.createTempDirectory("test").toFile(); 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..493289a8ab24 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/exploded-object-query-param.yaml @@ -0,0 +1,65 @@ +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 + # an array of objects, not exploded, so the whole slice is handed to the runtime + # as one value. That is the only shape that reaches the array branch: with + # explode: true the api unrolls the slice itself and each element arrives as a + # map. The flattening applies only to a map that is the parameter's own value, so + # an element here keeps the path it accumulated (arrayFilter[key]=value) rather + # than being keyed by its property names alone. + - in: query + name: arrayFilter + style: form + explode: false + schema: + type: array + items: + type: object + responses: + '200': + description: a list of items + content: + application/json: + schema: + type: array + items: + type: string diff --git a/samples/client/echo_api/go-external-refs/client.go b/samples/client/echo_api/go-external-refs/client.go index 83d93205607b..bf84706efe74 100644 --- a/samples/client/echo_api/go-external-refs/client.go +++ b/samples/client/echo_api/go-external-refs/client.go @@ -195,10 +195,16 @@ func parameterAddToHeaderOrQuery(headerOrQueryParams interface{}, keyPrefix stri for i:=0;i