diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go index 01e759b93..ad1c49597 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/api/petstore.gen.go @@ -51,10 +51,10 @@ type Pet struct { // FindPetsParams defines parameters for FindPets. type FindPetsParams struct { // tags to filter by - Tags *[]string `json:"tags,omitempty"` + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` // maximum number of results to return - Limit *int32 `json:"limit,omitempty"` + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } // AddPetJSONBody defines parameters for AddPet. diff --git a/examples/petstore-expanded/echo/api/petstore-types.gen.go b/examples/petstore-expanded/echo/api/petstore-types.gen.go index 8e7d9e200..d3d4d8a85 100644 --- a/examples/petstore-expanded/echo/api/petstore-types.gen.go +++ b/examples/petstore-expanded/echo/api/petstore-types.gen.go @@ -36,10 +36,10 @@ type Pet struct { // FindPetsParams defines parameters for FindPets. type FindPetsParams struct { // tags to filter by - Tags *[]string `json:"tags,omitempty"` + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` // maximum number of results to return - Limit *int32 `json:"limit,omitempty"` + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } // AddPetJSONBody defines parameters for AddPet. diff --git a/examples/petstore-expanded/gin/api/petstore-server.gen.go b/examples/petstore-expanded/gin/api/petstore-server.gen.go index 9ed0d50ee..eeba01a62 100644 --- a/examples/petstore-expanded/gin/api/petstore-server.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-server.gen.go @@ -158,7 +158,9 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options router.GET(options.BaseURL+"/pets/:id", wrapper.FindPetByID) return router -} // Base64 encoded, gzipped, json marshaled Swagger object +} + +// Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", diff --git a/examples/petstore-expanded/gin/api/petstore-types.gen.go b/examples/petstore-expanded/gin/api/petstore-types.gen.go index 24add6418..d3d4d8a85 100644 --- a/examples/petstore-expanded/gin/api/petstore-types.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-types.gen.go @@ -23,20 +23,23 @@ type NewPet struct { // Pet defines model for Pet. type Pet struct { - // Embedded struct due to allOf(#/components/schemas/NewPet) - NewPet `yaml:",inline"` - // Embedded fields due to inline allOf schema // Unique id of the pet Id int64 `json:"id"` + + // Name of the pet + Name string `json:"name"` + + // Type of the pet + Tag *string `json:"tag,omitempty"` } // FindPetsParams defines parameters for FindPets. type FindPetsParams struct { // tags to filter by - Tags *[]string `json:"tags,omitempty"` + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` // maximum number of results to return - Limit *int32 `json:"limit,omitempty"` + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } // AddPetJSONBody defines parameters for AddPet. diff --git a/examples/petstore-expanded/gin/api/petstore.go b/examples/petstore-expanded/gin/api/petstore.go index ae54a97bf..f36fcf00b 100644 --- a/examples/petstore-expanded/gin/api/petstore.go +++ b/examples/petstore-expanded/gin/api/petstore.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml + package api import ( diff --git a/examples/petstore-expanded/gin/api/server.cfg.yaml b/examples/petstore-expanded/gin/api/server.cfg.yaml index 1be3672c0..2e9b50666 100644 --- a/examples/petstore-expanded/gin/api/server.cfg.yaml +++ b/examples/petstore-expanded/gin/api/server.cfg.yaml @@ -1,4 +1,4 @@ -output: examples/petstore-expanded/gin/api/petstore-server.gen.go +output: petstore-server.gen.go generate: - gin - spec diff --git a/examples/petstore-expanded/gin/api/types.cfg.yaml b/examples/petstore-expanded/gin/api/types.cfg.yaml index 3769b143b..ad72dc318 100644 --- a/examples/petstore-expanded/gin/api/types.cfg.yaml +++ b/examples/petstore-expanded/gin/api/types.cfg.yaml @@ -1,4 +1,4 @@ -output: examples/petstore-expanded/gin/api/petstore-types.gen.go +output: petstore-types.gen.go generate: - types package: api diff --git a/examples/petstore-expanded/gin/petstore_test.go b/examples/petstore-expanded/gin/petstore_test.go index bc1e27b71..71bee77a8 100644 --- a/examples/petstore-expanded/gin/petstore_test.go +++ b/examples/petstore-expanded/gin/petstore_test.go @@ -83,9 +83,7 @@ func TestPetStore(t *testing.T) { store.Pets = map[int64]api.Pet{ 1: { - NewPet: api.NewPet{ - Tag: &tag, - }, + Tag: &tag, }, 2: {}, } diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go index 80eacf608..ab0fa90ee 100644 --- a/examples/petstore-expanded/petstore-client.gen.go +++ b/examples/petstore-expanded/petstore-client.gen.go @@ -50,10 +50,10 @@ type Pet struct { // FindPetsParams defines parameters for FindPets. type FindPetsParams struct { // tags to filter by - Tags *[]string `json:"tags,omitempty"` + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` // maximum number of results to return - Limit *int32 `json:"limit,omitempty"` + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } // AddPetJSONBody defines parameters for AddPet. diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 0c75382fc..7387fcf3a 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -182,7 +182,7 @@ type ParamsWithAddPropsParams struct { // turned into a proper type for additionalProperties to work P2 struct { Inner ParamsWithAddPropsParams_P2_Inner `json:"inner"` - } `json:"p2"` + } `form:"p2" json:"p2"` } // ParamsWithAddPropsParams_P2_Inner defines parameters for ParamsWithAddProps. diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go index 63feb8b32..6091ea6d4 100644 --- a/internal/test/parameters/parameters.gen.go +++ b/internal/test/parameters/parameters.gen.go @@ -37,28 +37,28 @@ type Object struct { // GetCookieParams defines parameters for GetCookie. type GetCookieParams struct { // primitive - P *int32 `json:"p,omitempty"` + P *int32 `form:"p,omitempty" json:"p,omitempty"` // primitive - Ep *int32 `json:"ep,omitempty"` + Ep *int32 `form:"ep,omitempty" json:"ep,omitempty"` // exploded array - Ea *[]int32 `json:"ea,omitempty"` + Ea *[]int32 `form:"ea,omitempty" json:"ea,omitempty"` // array - A *[]int32 `json:"a,omitempty"` + A *[]int32 `form:"a,omitempty" json:"a,omitempty"` // exploded object - Eo *Object `json:"eo,omitempty"` + Eo *Object `form:"eo,omitempty" json:"eo,omitempty"` // object - O *Object `json:"o,omitempty"` + O *Object `form:"o,omitempty" json:"o,omitempty"` // complex object - Co *ComplexObject `json:"co,omitempty"` + Co *ComplexObject `form:"co,omitempty" json:"co,omitempty"` // name starting with number - N1s *string `json:"1s,omitempty"` + N1s *string `form:"1s,omitempty" json:"1s,omitempty"` } // GetHeaderParams defines parameters for GetHeader. @@ -97,31 +97,31 @@ type GetDeepObjectParams struct { // GetQueryFormParams defines parameters for GetQueryForm. type GetQueryFormParams struct { // exploded array - Ea *[]int32 `json:"ea,omitempty"` + Ea *[]int32 `form:"ea,omitempty" json:"ea,omitempty"` // array - A *[]int32 `json:"a,omitempty"` + A *[]int32 `form:"a,omitempty" json:"a,omitempty"` // exploded object - Eo *Object `json:"eo,omitempty"` + Eo *Object `form:"eo,omitempty" json:"eo,omitempty"` // object - O *Object `json:"o,omitempty"` + O *Object `form:"o,omitempty" json:"o,omitempty"` // exploded primitive - Ep *int32 `json:"ep,omitempty"` + Ep *int32 `form:"ep,omitempty" json:"ep,omitempty"` // primitive - P *int32 `json:"p,omitempty"` + P *int32 `form:"p,omitempty" json:"p,omitempty"` // primitive string - Ps *string `json:"ps,omitempty"` + Ps *string `form:"ps,omitempty" json:"ps,omitempty"` // complex object - Co *ComplexObject `json:"co,omitempty"` + Co *ComplexObject `form:"co,omitempty" json:"co,omitempty"` // name starting with number - N1s *string `json:"1s,omitempty"` + N1s *string `form:"1s,omitempty" json:"1s,omitempty"` } // RequestEditorFn is the function signature for the RequestEditor callback function diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go index 3f4df3d75..20ff8cc5b 100644 --- a/internal/test/schemas/schemas.gen.go +++ b/internal/test/schemas/schemas.gen.go @@ -79,7 +79,7 @@ type Issue9JSONBody interface{} // Issue9Params defines parameters for Issue9. type Issue9Params struct { - Foo string `json:"foo"` + Foo string `form:"foo" json:"foo"` } // Issue185JSONRequestBody defines body for Issue185 for application/json ContentType. diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go index aa881693a..1f2f0591c 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -88,10 +88,10 @@ type SimpleResponse struct { // GetWithArgsParams defines parameters for GetWithArgs. type GetWithArgsParams struct { // An optional query argument - OptionalArgument *int64 `json:"optional_argument,omitempty"` + OptionalArgument *int64 `form:"optional_argument,omitempty" json:"optional_argument,omitempty"` // An optional query argument - RequiredArgument int64 `json:"required_argument"` + RequiredArgument int64 `form:"required_argument" json:"required_argument"` // An optional query argument HeaderArgument *int32 `json:"header_argument,omitempty"` @@ -109,7 +109,7 @@ type CreateResource2JSONBody Resource // CreateResource2Params defines parameters for CreateResource2. type CreateResource2Params struct { // Some query argument - InlineQueryArgument *int `json:"inline_query_argument,omitempty"` + InlineQueryArgument *int `form:"inline_query_argument,omitempty" json:"inline_query_argument,omitempty"` } // UpdateResource3JSONBody defines parameters for UpdateResource3. diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go index 2e02d0b59..be032e00c 100644 --- a/pkg/codegen/codegen_test.go +++ b/pkg/codegen/codegen_test.go @@ -2,16 +2,18 @@ package codegen import ( "bytes" + _ "embed" "go/format" "io/ioutil" "net/http" "testing" - examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded" - examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api" "github.com/getkin/kin-openapi/openapi3" "github.com/golangci/lint-1" "github.com/stretchr/testify/assert" + + examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded" + examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api" ) func TestExamplePetStoreCodeGeneration(t *testing.T) { @@ -160,7 +162,7 @@ type GetTestByNameResponse struct { // Check the client method signatures: assert.Contains(t, code, "type GetTestByNameParams struct {") - assert.Contains(t, code, "Top *int `json:\"$top,omitempty\"`") + assert.Contains(t, code, "Top *int `form:\"$top,omitempty\" json:\"$top,omitempty\"`") assert.Contains(t, code, "func (c *Client) GetTestByName(ctx context.Context, name string, params *GetTestByNameParams, reqEditors ...RequestEditorFn) (*http.Response, error) {") assert.Contains(t, code, "func (c *ClientWithResponses) GetTestByNameWithResponse(ctx context.Context, name string, params *GetTestByNameParams, reqEditors ...RequestEditorFn) (*GetTestByNameResponse, error) {") assert.Contains(t, code, "DeadSince *time.Time `json:\"dead_since,omitempty\" tag1:\"value1\" tag2:\"value2\"`") @@ -172,143 +174,5 @@ type GetTestByNameResponse struct { assert.Len(t, problems, 0) } -const testOpenAPIDefinition = ` -openapi: 3.0.1 - -info: - title: OpenAPI-CodeGen Test - description: 'This is a test OpenAPI Spec' - version: 1.0.0 - -servers: -- url: https://test.oapi-codegen.com/v2 -- url: http://test.oapi-codegen.com/v2 - -paths: - /test/{name}: - get: - tags: - - test - summary: Get test - operationId: getTestByName - parameters: - - name: name - in: path - required: true - schema: - type: string - - name: $top - in: query - required: false - schema: - type: integer - responses: - 200: - description: Success - content: - application/xml: - schema: - type: array - items: - $ref: '#/components/schemas/Test' - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Test' - 422: - description: InvalidArray - content: - application/xml: - schema: - type: array - application/json: - schema: - type: array - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cat: - get: - tags: - - cat - summary: Get cat status - operationId: getCatStatus - responses: - 200: - description: Success - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/CatAlive' - - $ref: '#/components/schemas/CatDead' - application/xml: - schema: - anyOf: - - $ref: '#/components/schemas/CatAlive' - - $ref: '#/components/schemas/CatDead' - application/yaml: - schema: - allOf: - - $ref: '#/components/schemas/CatAlive' - - $ref: '#/components/schemas/CatDead' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - schemas: - - Test: - properties: - name: - type: string - cases: - type: array - items: - $ref: '#/components/schemas/TestCase' - - TestCase: - properties: - name: - type: string - command: - type: string - - Error: - properties: - code: - type: integer - format: int32 - message: - type: string - - CatAlive: - properties: - name: - type: string - alive_since: - type: string - format: date-time - - CatDead: - properties: - name: - type: string - dead_since: - type: string - format: date-time - x-oapi-codegen-extra-tags: - tag1: value1 - tag2: value2 - cause: - type: string - enum: [car, dog, oldage] -` +//go:embed test_spec.yaml +var testOpenAPIDefinition string diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 7d40f4d5b..fe2d66be4 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -581,8 +581,8 @@ func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { return typeDefs } -// This defines the schema for a parameters definition object which encapsulates -// all the query, header and cookie parameters for an operation. +// GenerateParamsTypes defines the schema for a parameters definition object +// which encapsulates all the query, header and cookie parameters for an operation. func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { var typeDefs []TypeDefinition @@ -595,6 +595,7 @@ func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { s := Schema{} for _, param := range objectParams { pSchema := param.Schema + param.Style() if pSchema.HasAdditionalProperties { propRefName := strings.Join([]string{typeName, param.GoName()}, "_") pSchema.RefType = propRefName @@ -608,6 +609,7 @@ func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { JsonFieldName: param.ParamName, Required: param.Required, Schema: pSchema, + NeedsFormTag: param.Style() == "form", ExtensionProps: ¶m.Spec.ExtensionProps, } s.Properties = append(s.Properties, prop) diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 27e08848c..13f2a81d1 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -74,6 +74,7 @@ type Property struct { Nullable bool ReadOnly bool WriteOnly bool + NeedsFormTag bool ExtensionProps *openapi3.ExtensionProps } @@ -484,20 +485,27 @@ func GenFieldsFromProperties(props []Property) []string { field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef()) // Support x-omitempty - omitEmpty := true + overrideOmitEmpty := true if _, ok := p.ExtensionProps.Extensions[extPropOmitEmpty]; ok { if extOmitEmpty, err := extParseOmitEmpty(p.ExtensionProps.Extensions[extPropOmitEmpty]); err == nil { - omitEmpty = extOmitEmpty + overrideOmitEmpty = extOmitEmpty } } fieldTags := make(map[string]string) - if (p.Required && !p.ReadOnly && !p.WriteOnly) || p.Nullable || !omitEmpty { + if (p.Required && !p.ReadOnly && !p.WriteOnly) || p.Nullable || !overrideOmitEmpty { fieldTags["json"] = p.JsonFieldName + if p.NeedsFormTag { + fieldTags["form"] = p.JsonFieldName + } } else { fieldTags["json"] = p.JsonFieldName + ",omitempty" + if p.NeedsFormTag { + fieldTags["form"] = p.JsonFieldName + ",omitempty" + } } + if extension, ok := p.ExtensionProps.Extensions[extPropExtraTags]; ok { if tags, err := extExtraTags(extension); err == nil { keys := SortedStringKeys(tags) diff --git a/pkg/codegen/test_spec.yaml b/pkg/codegen/test_spec.yaml new file mode 100644 index 000000000..b6903e5e3 --- /dev/null +++ b/pkg/codegen/test_spec.yaml @@ -0,0 +1,138 @@ +openapi: 3.0.1 + +info: + title: OpenAPI-CodeGen Test + description: 'This is a test OpenAPI Spec' + version: 1.0.0 + +servers: + - url: https://test.oapi-codegen.com/v2 + - url: http://test.oapi-codegen.com/v2 + +paths: + /test/{name}: + get: + tags: + - test + summary: Get test + operationId: getTestByName + parameters: + - name: name + in: path + required: true + schema: + type: string + - name: $top + in: query + required: false + schema: + type: integer + responses: + 200: + description: Success + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Test' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Test' + 422: + description: InvalidArray + content: + application/xml: + schema: + type: array + application/json: + schema: + type: array + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cat: + get: + tags: + - cat + summary: Get cat status + operationId: getCatStatus + responses: + 200: + description: Success + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/CatAlive' + - $ref: '#/components/schemas/CatDead' + application/xml: + schema: + anyOf: + - $ref: '#/components/schemas/CatAlive' + - $ref: '#/components/schemas/CatDead' + application/yaml: + schema: + allOf: + - $ref: '#/components/schemas/CatAlive' + - $ref: '#/components/schemas/CatDead' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + schemas: + + Test: + properties: + name: + type: string + cases: + type: array + items: + $ref: '#/components/schemas/TestCase' + + TestCase: + properties: + name: + type: string + command: + type: string + + Error: + properties: + code: + type: integer + format: int32 + message: + type: string + + CatAlive: + properties: + name: + type: string + alive_since: + type: string + format: date-time + + CatDead: + properties: + name: + type: string + dead_since: + type: string + format: date-time + x-oapi-codegen-extra-tags: + tag1: value1 + tag2: value2 + cause: + type: string + enum: [car, dog, oldage]