From c2a443bf5e8b03bf4335efaf4cafc7e1aabea62b Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 7 Nov 2021 12:22:53 +0300 Subject: [PATCH 01/19] Added strict server generation that does automatic marshaling of request and response bodies, meaning less manual code and forced schema compliance --- cmd/oapi-codegen/oapi-codegen.go | 2 + .../strict/api/petstore.gen.go | 676 ++++++++++++++++++ .../petstore-expanded/strict/api/petstore.go | 104 +++ examples/petstore-expanded/strict/petstore.go | 56 ++ .../petstore-expanded/strict/petstore_test.go | 170 +++++ go.sum | 2 - internal/test/client/client.gen.go | 83 +++ internal/test/components/components.gen.go | 38 + pkg/codegen/codegen.go | 16 + pkg/codegen/operations.go | 138 ++++ pkg/codegen/schema.go | 3 + pkg/codegen/templates/client.tmpl | 2 +- pkg/codegen/templates/strict/strict-chi.tmpl | 124 ++++ pkg/codegen/templates/strict/strict-echo.tmpl | 97 +++ pkg/codegen/templates/strict/strict-gin.tmpl | 99 +++ .../templates/strict/strict-interface.tmpl | 76 ++ pkg/codegen/templates/templates.gen.go | 402 ++++++++++- 17 files changed, 2084 insertions(+), 4 deletions(-) create mode 100644 examples/petstore-expanded/strict/api/petstore.gen.go create mode 100644 examples/petstore-expanded/strict/api/petstore.go create mode 100644 examples/petstore-expanded/strict/petstore.go create mode 100644 examples/petstore-expanded/strict/petstore_test.go create mode 100644 pkg/codegen/templates/strict/strict-chi.tmpl create mode 100644 pkg/codegen/templates/strict/strict-echo.tmpl create mode 100644 pkg/codegen/templates/strict/strict-gin.tmpl create mode 100644 pkg/codegen/templates/strict/strict-interface.tmpl diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index 49c8747606..87978f6321 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -116,6 +116,8 @@ func main() { opts.GenerateEchoServer = true case "gin": opts.GenerateGinServer = true + case "strict-server": + opts.GenerateStrict = true case "types": opts.GenerateTypes = true case "spec": diff --git a/examples/petstore-expanded/strict/api/petstore.gen.go b/examples/petstore-expanded/strict/api/petstore.gen.go new file mode 100644 index 0000000000..ad03e9f5dc --- /dev/null +++ b/examples/petstore-expanded/strict/api/petstore.gen.go @@ -0,0 +1,676 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" +) + +// Error defines model for Error. +type Error struct { + // Error code + Code int32 `json:"code"` + + // Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name of the pet + Name string `json:"name"` + + // Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// 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"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // tags to filter by + Tags *[]string `json:"tags,omitempty"` + + // maximum number of results to return + Limit *int32 `json:"limit,omitempty"` +} + +// AddPetJSONBody defines parameters for AddPet. +type AddPetJSONBody NewPet + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody AddPetJSONBody + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) + // Creates a new pet + // (POST /pets) + AddPet(w http.ResponseWriter, r *http.Request) + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(w http.ResponseWriter, r *http.Request, id int64) + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(w http.ResponseWriter, r *http.Request, id int64) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc + +// FindPets operation middleware +func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + // ------------- Optional query parameter "tags" ------------- + if paramValue := r.URL.Query().Get("tags"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), ¶ms.Tags) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "tags", Err: err}) + return + } + + // ------------- Optional query parameter "limit" ------------- + if paramValue := r.URL.Query().Get("limit"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), ¶ms.Limit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.FindPets(w, r, params) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// AddPet operation middleware +func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.AddPet(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// DeletePet operation middleware +func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeletePet(w, r, id) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// FindPetByID operation middleware +func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.FindPetByID(w, r, id) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/pets", wrapper.FindPets) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/pets", wrapper.AddPet) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/pets/{id}", wrapper.DeletePet) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/pets/{id}", wrapper.FindPetByID) + }) + + return r +} + +type FindPetsRequestObject struct { + Params FindPetsParams +} + +type FindPets200JSONResponse []Pet + +func (t FindPets200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(([]Pet)(t)) +} + +type FindPetsdefaultJSONResponse struct { + Body Error + StatusCode int +} + +func (t FindPetsdefaultJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type AddPetRequestObject struct { + Body *AddPetJSONRequestBody +} + +type AddPet200JSONResponse Pet + +func (t AddPet200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Pet)(t)) +} + +type AddPetdefaultJSONResponse struct { + Body Error + StatusCode int +} + +func (t AddPetdefaultJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type DeletePetRequestObject struct { + Id int64 `json:"id"` +} + +type DeletePet204Response struct { +} + +type DeletePetdefaultJSONResponse struct { + Body Error + StatusCode int +} + +func (t DeletePetdefaultJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type FindPetByIDRequestObject struct { + Id int64 `json:"id"` +} + +type FindPetByID200JSONResponse Pet + +func (t FindPetByID200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Pet)(t)) +} + +type FindPetByIDdefaultJSONResponse struct { + Body Error + StatusCode int +} + +func (t FindPetByIDdefaultJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(ctx context.Context, request FindPetsRequestObject) interface{} + // Creates a new pet + // (POST /pets) + AddPet(ctx context.Context, request AddPetRequestObject) interface{} + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(ctx context.Context, request DeletePetRequestObject) interface{} + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(ctx context.Context, request FindPetByIDRequestObject) interface{} +} + +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// FindPets operation middleware +func (sh *strictHandler) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) { + var request FindPetsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.FindPets(ctx, request.(FindPetsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "FindPets") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case FindPets200JSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case FindPetsdefaultJSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(v.StatusCode) + writeJSON(w, v) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// AddPet operation middleware +func (sh *strictHandler) AddPet(w http.ResponseWriter, r *http.Request) { + var request AddPetRequestObject + + var body AddPetJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.AddPet(ctx, request.(AddPetRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "AddPet") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case AddPet200JSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case AddPetdefaultJSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(v.StatusCode) + writeJSON(w, v) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// DeletePet operation middleware +func (sh *strictHandler) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { + var request DeletePetRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.DeletePet(ctx, request.(DeletePetRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeletePet") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case DeletePet204Response: + w.WriteHeader(204) + case DeletePetdefaultJSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(v.StatusCode) + writeJSON(w, v) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// FindPetByID operation middleware +func (sh *strictHandler) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) { + var request FindPetByIDRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.FindPetByID(ctx, request.(FindPetByIDRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "FindPetByID") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case FindPetByID200JSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case FindPetByIDdefaultJSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(v.StatusCode) + writeJSON(w, v) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +func writeJSON(w http.ResponseWriter, v interface{}) { + if err := json.NewEncoder(w).Encode(v); err != nil { + fmt.Fprintln(w, err) + } +} + +func writeRaw(w http.ResponseWriter, b []byte) { + if _, err := w.Write(b); err != nil { + fmt.Fprintln(w, err) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+RXW48budH9KwV+32OnNbEXedBTvB4vICBrT+LdvKznoYZdkmrBSw9Z1FgY6L8HRbZu", + "I3k2QYIgQV506WY1T51zqlj9bGz0YwwUJJv5s8l2TR7rzw8pxaQ/xhRHSsJUL9s4kH4PlG3iUTgGM2+L", + "od7rzDImj2LmhoO8fWM6I9uR2l9aUTK7znjKGVfffND+9iE0S+KwMrtdZxI9Fk40mPkvZtpwv/x+15mP", + "9HRHcok7oL+y3Uf0BHEJsiYYSS437Izg6jLup+34etwLoHV3hTdhQ+c+Lc38l2fz/4mWZm7+b3YUYjap", + "MJty2XUvk+HhEtLPgR8LAQ/nuE7F+MN3V8R4gZQHc7+73+llDsvYJA+CtuImj+zM3ODIQuj/mJ9wtaLU", + "czTdRLH53K7Bu7sF/EToTWdK0qC1yDifzU5idt2LJN5BRj86qsGyRoGSKQNqMlliIsAMGIC+tmUSYSAf", + "Q5aEQrAklJIoA4dKwaeRgj7pbX8DeSTLS7ZYt+qMY0sh09Eb5t2Idk3wpr85g5zns9nT01OP9XYf02o2", + "xebZnxbvP3z8/OF3b/qbfi3eVcNQ8vnT8jOlDVu6lvesLpmpGCzulLO7KU3TmQ2l3Ej5fX/T3+iT40gB", + "RzZz87Ze6syIsq6OmClB+mPVDHZO619ISgoZ0LnKJCxT9JWhvM1CvlGt/0umBGsl2VrKGSR+CR/RQ6YB", + "bAwDewpSPFCWHn5EshQwg5AfY4KMKxbhDBlHptBBIAtpHYMtGTL5kwUsgJ6kh3cUCAOgwCrhhgcELKtC", + "HaAFRlsc19Ae3peEDywlQRw4gouJfAcxBUwEtCIBcjShC2Q7sCXlkrUgHFkpuYfbwhk8g5Q0cu5gLG7D", + "AZPuRSlq0h0IB8tDCQIbTFwy/FqyxB4WAdZoYa0gMGeC0aEQwsBWilc6Fq2kNBcceORsOawAg2g2x9wd", + "r4rDQ+bjGhNJwj2Juh58dJSFCdiPlAZWpv7KG/QtIXT8WNDDwKjMJMzwqLltyLFAiAEkJolJKeElheGw", + "ew93CSlTEIVJgf0RQEkBYRNdkREFNhQooAJu5OqHx5L0GYtwfPKS0sT6Ei07zmeb1B30ozvqayHHAR2p", + "sEOnPFpKKJqYfvfwueSRwsDKskM1zxBdTJ06MJMVdXPNslpFs+5gQ2u2xSFoY0tD8eD4gVLs4ceYHhio", + "cPZxOJVBb1djO7QcGPsv4Uv4TENVomRYkprPxYeYagDFo2NSkVR8D1obHusDJ/I5uw6onFVLkxxcUR+q", + "O3u4W2Mm51phjJSm8EpzlZcEllgsP5RGOO730XWn8Rtyk3S8oZSwO99a6wR46A6FGPhh3cPPAiM5R0Eo", + "67kxxlxIK2lfRD0oFbivAi26PZf7J+3Tqkx2FcjBFqEEC5I4Sz2WNixIPfxQsiUgqd1gKHyoAu0U2ZKj", + "xBVO8+8+wKtbClbz2OIzBvC40pTJTWr18OfSQn10qltTj0rzzhFKd2g+gMVqkbSVkz1b2pM5piZzqEY1", + "iwoMHLojlKlwA2feA86KwbKUgRVqzghF9j6bhGw7nZFW9+vh7lSYytyEcUwkXPxJ52qmKd2Jv7X19l/0", + "iNORoR53i8HMzQ8cBj1f6rGRlABKuc4g54eF4Er7PizZCSV42BodBczcPBZK2+M5r+tMN42MdSoR8vUM", + "upyh2gVMCbf6P8u2Hns6nNTx5hyBx6/stY0X/0BJ55lEuTipsFI9y76BybFnOQP1m8Po7l4HoDxqa6no", + "39zc7KceCm1aG0c3DQ6zX7NCfL6W9mujXJvjXhCxu5h/RhLYg2nT0RKLk38Iz2sw2lB/ZeMS6OuorVV7", + "cFvTmVy8x7S9MkAotjHmK6PG+0QodWQL9KRr97NYnWv0DG7YdYmOc87FJxouzPpuUK+aNptSlu/jsP2X", + "sbCfqy9puCNRj+Ew6NcBtjmdkSUV2v2TnvlNq/z3WONC8Hq/zqOzZx52zSKO5MrrV7uusZnDytV3FnhA", + "bbOxuWZxC7loTlc8clujm01e7WiLW+0hY9N2wjL1Dx2gj+2Dhwulv9VLrr9LXfaS7y6zViANxfCfJOTt", + "QYyqwhYWtwrv9ReKc8UOOi5uv3X8fL+t9/5+vZYkdv1vk+t/toxfKNrUr0sobfYynb3H71/J+5MXW307", + "3d3v/hYAAP//wO3O5VcSAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/petstore-expanded/strict/api/petstore.go b/examples/petstore-expanded/strict/api/petstore.go new file mode 100644 index 0000000000..e9cf9b99ea --- /dev/null +++ b/examples/petstore-expanded/strict/api/petstore.go @@ -0,0 +1,104 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,strict-server,spec -o petstore.gen.go ../../petstore-expanded.yaml + +package api + +import ( + "context" + "fmt" + "net/http" + "sync" +) + +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +// Make sure we conform to StrictServerInterface + +var _ StrictServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// Here, we implement all of the handlers in the ServerInterface +func (p *PetStore) FindPets(ctx context.Context, request FindPetsRequestObject) interface{} { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if request.Params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *request.Params.Tags { + if pet.Tag != nil && (*pet.Tag == t) { + result = append(result, pet) + } + } + } else { + // Add all pets if we're not filtering + result = append(result, pet) + } + + if request.Params.Limit != nil { + l := int(*request.Params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + return FindPets200JSONResponse(result) +} + +func (p *PetStore) AddPet(ctx context.Context, request AddPetRequestObject) interface{} { + // We now have a pet, let's add it to our "database". + // We're always asynchronous, so lock unsafe operations below + p.Lock.Lock() + defer p.Lock.Unlock() + + // We handle pets, not NewPets, which have an additional ID field + var pet Pet + pet.Name = request.Body.Name + pet.Tag = request.Body.Tag + pet.Id = p.NextId + p.NextId = p.NextId + 1 + + // Insert into map + p.Pets[pet.Id] = pet + + // Now, we have to return the NewPet + return AddPet200JSONResponse(pet) +} + +func (p *PetStore) FindPetByID(ctx context.Context, request FindPetByIDRequestObject) interface{} { + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[request.Id] + if !found { + return FindPetByIDdefaultJSONResponse{StatusCode: http.StatusNotFound, Body:Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} + } + + return FindPetByID200JSONResponse(pet) +} + +func (p *PetStore) DeletePet(ctx context.Context, request DeletePetRequestObject) interface{} { + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[request.Id] + if !found { + return DeletePetdefaultJSONResponse{StatusCode: http.StatusNotFound, Body:Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} + } + delete(p.Pets, request.Id) + + return DeletePet204Response{} +} diff --git a/examples/petstore-expanded/strict/petstore.go b/examples/petstore-expanded/strict/petstore.go new file mode 100644 index 0000000000..4e33e9e19e --- /dev/null +++ b/examples/petstore-expanded/strict/petstore.go @@ -0,0 +1,56 @@ +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + + "github.com/go-chi/chi/v5" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/strict/api" + middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" +) + +func main() { + var port = flag.Int("port", 8080, "Port for test HTTP server") + flag.Parse() + + swagger, err := api.GetSwagger() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) + os.Exit(1) + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + petStoreStrictHandler := api.NewStrictHandler(petStore, nil) + + // This is how you set up a basic chi router + r := chi.NewRouter() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + r.Use(middleware.OapiRequestValidator(swagger)) + + // We now register our petStore above as the handler for the interface + api.HandlerFromMux(petStoreStrictHandler, r) + + s := &http.Server{ + Handler: r, + Addr: fmt.Sprintf("0.0.0.0:%d", *port), + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/petstore-expanded/strict/petstore_test.go b/examples/petstore-expanded/strict/petstore_test.go new file mode 100644 index 0000000000..a970ff02b2 --- /dev/null +++ b/examples/petstore-expanded/strict/petstore_test.go @@ -0,0 +1,170 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/strict/api" + middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" + "github.com/deepmap/oapi-codegen/pkg/testutil" +) + +func doGet(t *testing.T, mux *chi.Mux, url string) *httptest.ResponseRecorder { + response := testutil.NewRequest().Get(url).WithAcceptJson().GoWithHTTPHandler(t, mux) + return response.Recorder +} + +func TestPetStore(t *testing.T) { + var err error + + // Get the swagger description of our API + swagger, err := api.GetSwagger() + require.NoError(t, err) + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + // This is how you set up a basic chi router + r := chi.NewRouter() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + r.Use(middleware.OapiRequestValidator(swagger)) + + store := api.NewPetStore() + api.HandlerFromMux(api.NewStrictHandler(store, nil), r) + + t.Run("Add pet", func(t *testing.T) { + tag := "TagOfSpot" + newPet := api.NewPet{ + Name: "Spot", + Tag: &tag, + } + + rr := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, r).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + }) + + t.Run("Find pet by ID", func(t *testing.T) { + pet := api.Pet{ + Id: 100, + } + + store.Pets[pet.Id] = pet + rr := doGet(t, r, fmt.Sprintf("/pets/%d", pet.Id)) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, pet, resultPet) + }) + + t.Run("Pet not found", func(t *testing.T) { + rr := doGet(t, r, "/pets/27179095781") + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + }) + + t.Run("List all pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Now, list all pets, we should have two + rr := doGet(t, r, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + tag := "TagOfFido" + + store.Pets = map[int64]api.Pet{ + 1: api.Pet{ + NewPet: api.NewPet{ + Tag: &tag, + }, + }, + 2: api.Pet{}, + } + + // Filter pets by tag, we should have 1 + rr := doGet(t, r, "/pets?tags=TagOfFido") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Filter pets by non existent tag, we should have 0 + rr := doGet(t, r, "/pets?tags=NotExists") + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) + + t.Run("Delete pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Let's delete non-existent pet + rr := testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, r).Recorder + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + rr = testutil.NewRequest().Delete("/pets/1").GoWithHTTPHandler(t, r).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + rr = testutil.NewRequest().Delete("/pets/2").GoWithHTTPHandler(t, r).Recorder + assert.Equal(t, http.StatusNoContent, rr.Code) + + // Should have no pets left. + var petList []api.Pet + rr = doGet(t, r, "/pets") + assert.Equal(t, http.StatusOK, rr.Code) + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) +} diff --git a/go.sum b/go.sum index f43fac9890..90b52ff8a7 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1: github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/getkin/kin-openapi v0.61.0 h1:6awGqF5nG5zkVpMsAih1QH4VgzS8phTxECUWIFo7zko= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.80.0 h1:W/s5/DNnDCR8P+pYyafEWlGk4S7/AfQUWXgrRSSAzf8= github.com/getkin/kin-openapi v0.80.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index abea3b4db2..7ecf2b6fe1 100644 --- a/internal/test/client/client.gen.go +++ b/internal/test/client/client.gen.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "io/ioutil" + "mime/multipart" "net/http" "net/url" "path" @@ -34,15 +35,27 @@ type SchemaObject struct { // PostBothJSONBody defines parameters for PostBoth. type PostBothJSONBody SchemaObject +// PostBothBinaryBody defines parameters for PostBoth. +type PostBothBinaryBody *multipart.FileHeader + // PostJsonJSONBody defines parameters for PostJson. type PostJsonJSONBody SchemaObject +// PostOtherBinaryBody defines parameters for PostOther. +type PostOtherBinaryBody *multipart.FileHeader + // PostBothJSONRequestBody defines body for PostBoth for application/json ContentType. type PostBothJSONRequestBody PostBothJSONBody +// PostBothBinaryRequestBody defines body for PostBoth for application/json ContentType. +type PostBothBinaryRequestBody PostBothBinaryBody + // PostJsonJSONRequestBody defines body for PostJson for application/json ContentType. type PostJsonJSONRequestBody PostJsonJSONBody +// PostOtherBinaryRequestBody defines body for PostOther for application/json ContentType. +type PostOtherBinaryRequestBody PostOtherBinaryBody + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -121,6 +134,8 @@ type ClientInterface interface { PostBoth(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostBothWithBinaryBody(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetBoth request GetBoth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -135,6 +150,8 @@ type ClientInterface interface { // PostOther request with any body PostOtherWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostOtherWithBinaryBody(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetOther request GetOther(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -166,6 +183,18 @@ func (c *Client) PostBoth(ctx context.Context, body PostBothJSONRequestBody, req return c.Client.Do(req) } +func (c *Client) PostBothWithBinaryBody(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostBothRequestWithBinaryBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetBoth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetBothRequest(c.Server) if err != nil { @@ -226,6 +255,18 @@ func (c *Client) PostOtherWithBody(ctx context.Context, contentType string, body return c.Client.Do(req) } +func (c *Client) PostOtherWithBinaryBody(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostOtherRequestWithBinaryBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetOther(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetOtherRequest(c.Server) if err != nil { @@ -261,6 +302,17 @@ func NewPostBothRequest(server string, body PostBothJSONRequestBody) (*http.Requ return NewPostBothRequestWithBody(server, "application/json", bodyReader) } +// NewPostBothRequestWithBinaryBody calls the generic PostBoth builder with application/octet-stream body +func NewPostBothRequestWithBinaryBody(server string, body PostBothBinaryRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostBothRequestWithBody(server, "application/octet-stream", bodyReader) +} + // NewPostBothRequestWithBody generates requests for PostBoth with any type of body func NewPostBothRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -384,6 +436,17 @@ func NewGetJsonRequest(server string) (*http.Request, error) { return req, nil } +// NewPostOtherRequestWithBinaryBody calls the generic PostOther builder with application/octet-stream body +func NewPostOtherRequestWithBinaryBody(server string, body PostOtherBinaryRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostOtherRequestWithBody(server, "application/octet-stream", bodyReader) +} + // NewPostOtherRequestWithBody generates requests for PostOther with any type of body func NewPostOtherRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -515,6 +578,8 @@ type ClientWithResponsesInterface interface { PostBothWithResponse(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) + PostBothWithBinaryBodyWithResponse(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) + // GetBoth request GetBothWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetBothResponse, error) @@ -529,6 +594,8 @@ type ClientWithResponsesInterface interface { // PostOther request with any body PostOtherWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) + PostOtherWithBinaryBodyWithResponse(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) + // GetOther request GetOtherWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOtherResponse, error) @@ -700,6 +767,14 @@ func (c *ClientWithResponses) PostBothWithResponse(ctx context.Context, body Pos return ParsePostBothResponse(rsp) } +func (c *ClientWithResponses) PostBothWithBinaryBodyWithResponse(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) { + rsp, err := c.PostBothWithBinaryBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostBothResponse(rsp) +} + // GetBothWithResponse request returning *GetBothResponse func (c *ClientWithResponses) GetBothWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetBothResponse, error) { rsp, err := c.GetBoth(ctx, reqEditors...) @@ -744,6 +819,14 @@ func (c *ClientWithResponses) PostOtherWithBodyWithResponse(ctx context.Context, return ParsePostOtherResponse(rsp) } +func (c *ClientWithResponses) PostOtherWithBinaryBodyWithResponse(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) { + rsp, err := c.PostOtherWithBinaryBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostOtherResponse(rsp) +} + // GetOtherWithResponse request returning *GetOtherResponse func (c *ClientWithResponses) GetOtherWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOtherResponse, error) { rsp, err := c.GetOther(ctx, reqEditors...) diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 54f9eb2e11..c7295d36fc 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -117,6 +117,9 @@ type BodyWithAddPropsJSONBody_Inner struct { AdditionalProperties map[string]int `json:"-"` } +// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. +type EnsureEverythingIsReferencedTextRequestBody RequestBody + // EnsureEverythingIsReferencedJSONRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. type EnsureEverythingIsReferencedJSONRequestBody RequestBody @@ -795,6 +798,8 @@ type ClientInterface interface { // EnsureEverythingIsReferenced request with any body EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // ParamsWithAddProps request @@ -818,6 +823,18 @@ func (c *Client) EnsureEverythingIsReferencedWithBody(ctx context.Context, conte return c.Client.Do(req) } +func (c *Client) EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnsureEverythingIsReferencedRequestWithTextBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewEnsureEverythingIsReferencedRequest(c.Server, body) if err != nil { @@ -866,6 +883,17 @@ func (c *Client) BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSON return c.Client.Do(req) } +// NewEnsureEverythingIsReferencedRequestWithTextBody calls the generic EnsureEverythingIsReferenced builder with text/plain body +func NewEnsureEverythingIsReferencedRequestWithTextBody(server string, body EnsureEverythingIsReferencedTextRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewEnsureEverythingIsReferencedRequestWithBody(server, "text/plain", bodyReader) +} + // NewEnsureEverythingIsReferencedRequest calls the generic EnsureEverythingIsReferenced builder with application/json body func NewEnsureEverythingIsReferencedRequest(server string, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -1047,6 +1075,8 @@ type ClientWithResponsesInterface interface { // EnsureEverythingIsReferenced request with any body EnsureEverythingIsReferencedWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) + EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) + EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) // ParamsWithAddProps request @@ -1150,6 +1180,14 @@ func (c *ClientWithResponses) EnsureEverythingIsReferencedWithBodyWithResponse(c return ParseEnsureEverythingIsReferencedResponse(rsp) } +func (c *ClientWithResponses) EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { + rsp, err := c.EnsureEverythingIsReferencedWithTextBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseEnsureEverythingIsReferencedResponse(rsp) +} + func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { rsp, err := c.EnsureEverythingIsReferenced(ctx, body, reqEditors...) if err != nil { diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index af70b19dcf..7c3f5affe3 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -34,6 +34,7 @@ type Options struct { GenerateChiServer bool // GenerateChiServer specifies whether to generate chi server boilerplate GenerateEchoServer bool // GenerateEchoServer specifies whether to generate echo server boilerplate GenerateGinServer bool // GenerateGinServer specifies whether to generate echo server boilerplate + GenerateStrict bool //GenerateStrict specifies whether to generate strict server wrapper GenerateClient bool // GenerateClient specifies whether to generate client boilerplate GenerateTypes bool // GenerateTypes specifies whether to generate type definitions EmbedSpec bool // Whether to embed the swagger spec in the generated code @@ -174,6 +175,14 @@ func Generate(swagger *openapi3.T, packageName string, opts Options) (string, er } } + var strictServerOut string + if opts.GenerateStrict { + strictServerOut, err = GenerateStrictServer(t, ops, opts) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var clientOut string if opts.GenerateClient { clientOut, err = GenerateClient(t, ops) @@ -255,6 +264,13 @@ func Generate(swagger *openapi3.T, packageName string, opts Options) (string, er } } + if opts.GenerateStrict { + _, err = w.WriteString(strictServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.EmbedSpec { _, err = w.WriteString(inlinedSpec) if err != nil { diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 408b5df435..a4ea0113d9 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -17,6 +17,7 @@ import ( "bufio" "bytes" "fmt" + "strconv" "strings" "text/template" "unicode" @@ -205,6 +206,7 @@ type OperationDefinition struct { SecurityDefinitions []SecurityDefinition // These are the security providers BodyRequired bool Bodies []RequestBodyDefinition // The list of bodies for which to generate handlers. + Responses []ResponseDefinition // The list of responses that can be accepted by handlers. Summary string // Summary string from Swagger, used to generate a comment Method string // GET, POST, DELETE, etc. Path string // The Swagger path for the operation, like /resource/{id} @@ -361,6 +363,44 @@ func (r RequestBodyDefinition) Suffix() string { return "With" + r.NameTag + "Body" } +type ResponseDefinition struct { + StatusCode string + Description string + Contents []ResponseContentDefinition + Headers []ResponseHeaderDefinition +} + +func (r ResponseDefinition) HasFixedStatusCode() bool { + _, err := strconv.Atoi(r.StatusCode) + return err == nil +} + +type ResponseContentDefinition struct { + // This is the schema describing this content + Schema Schema + + // This is the content type corresponding to the body, eg, application/json + ContentType string + + // When we generate type names, we need a Tag for it, such as JSON, in + // which case we will produce "Response200JSONContent". + NameTag string +} + +// TypeDef returns the Go type definition for a request body +func (r ResponseContentDefinition) TypeDef(opID string, statusCode int) *TypeDefinition { + return &TypeDefinition{ + TypeName: fmt.Sprintf("%s%v%sResponse", opID, statusCode, r.NameTag), + Schema: r.Schema, + } +} + +type ResponseHeaderDefinition struct { + Name string + GoName string + Schema Schema +} + // This function returns the subset of the specified parameters which are of the // specified type. func FilterParameterDefinitionByType(params []ParameterDefinition, in string) []ParameterDefinition { @@ -431,6 +471,11 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { return nil, fmt.Errorf("error generating body definitions: %w", err) } + responseDefinitions, err := GenerateResponseDefinitions(op.OperationID, op.Responses) + if err != nil { + return nil, fmt.Errorf("error generating response definitions: %w", err) + } + opDef := OperationDefinition{ PathParams: pathParams, HeaderParams: FilterParameterDefinitionByType(allParams, "header"), @@ -443,6 +488,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { Path: requestPath, Spec: op, Bodies: bodyDefinitions, + Responses: responseDefinitions, TypeDefinitions: typeDefinitions, } @@ -512,6 +558,12 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody case "application/json": tag = "JSON" defaultBody = true + case "multipart/form-data": + tag = "Formdata" + case "text/plain": + tag = "Text" + case "application/octet-stream": + tag = "Binary" default: continue } @@ -557,6 +609,78 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody return bodyDefinitions, typeDefinitions, nil } +func GenerateResponseDefinitions(operationID string, responses openapi3.Responses) ([]ResponseDefinition, error) { + var responseDefinitions []ResponseDefinition + + for statusCode, responseOrRef := range responses { + if responseOrRef == nil { + continue + } + response := responseOrRef.Value + + var responseContentDefinitions []ResponseContentDefinition + + for contentType, content := range response.Content { + var tag string + switch contentType { + case "application/json": + tag = "JSON" + case "text/plain": + tag = "Text" + case "application/octet-stream": + tag = "Binary" + default: + continue + } + + responseTypeName := operationID + statusCode + tag + "Response" + contentSchema, err := GenerateGoSchema(content.Schema, []string{responseTypeName}) + if err != nil { + return nil, fmt.Errorf("error generating request body definition: %w", err) + } + + // If the response is a pre-defined type + if IsGoTypeReference(responseOrRef.Ref) { + // Convert the reference path to Go type + refType, err := RefPathToGoType(responseOrRef.Ref) + if err != nil { + return nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", responseOrRef.Ref, err) + } + contentSchema.RefType = refType + } + + rcd := ResponseContentDefinition{ + ContentType: contentType, + NameTag: tag, + Schema: contentSchema, + } + responseContentDefinitions = append(responseContentDefinitions, rcd) + } + + var responseHeaderDefinitions []ResponseHeaderDefinition + for headerName, header := range response.Headers { + contentSchema, err := GenerateGoSchema(header.Value.Schema, []string{}) + if err != nil { + return nil, fmt.Errorf("error generating response header definition: %w", err) + } + headerDefinition := ResponseHeaderDefinition{Name: headerName, GoName: ToCamelCase(headerName), Schema: contentSchema} + responseHeaderDefinitions = append(responseHeaderDefinitions, headerDefinition) + } + + rd := ResponseDefinition{ + StatusCode: statusCode, + Contents: responseContentDefinitions, + Headers: responseHeaderDefinitions, + } + if response.Description != nil { + rd.Description = *response.Description + } + responseDefinitions = append(responseDefinitions, rd) + } + + return responseDefinitions, nil +} + func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { var typeDefs []TypeDefinition // Start with the params object itself @@ -675,6 +799,20 @@ func GenerateGinServer(t *template.Template, operations []OperationDefinition) ( return GenerateTemplates([]string{"gin-interface.tmpl", "gin-wrappers.tmpl", "gin-register.tmpl"}, t, operations) } +func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Options) (string, error) { + templates := []string{"strict-interface.tmpl"} + if opts.GenerateChiServer { + templates = append(templates, "strict-chi.tmpl") + } + if opts.GenerateEchoServer { + templates = append(templates, "strict-echo.tmpl") + } + if opts.GenerateGinServer { + templates = append(templates, "strict-gin.tmpl") + } + return GenerateTemplates(templates, t, operations) +} + // Uses the template engine to generate the function which registers our wrappers // as Echo path handlers. func GenerateClient(t *template.Template, ops []OperationDefinition) (string, error) { diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index b2b87ae3f4..246f7e7218 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -392,6 +392,9 @@ func resolveType(schema *openapi3.Schema, path []string, outSchema *Schema) erro case "json": outSchema.GoType = "json.RawMessage" outSchema.SkipOptionalPointer = true + case "binary": + outSchema.GoType = "*multipart.FileHeader" + outSchema.SkipOptionalPointer = true default: // All unrecognized formats are simply a regular string. outSchema.GoType = "string" diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index b08172881b..7a22eb632b 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -104,7 +104,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge {{range .Bodies}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { - req, err := New{{$opid}}{{.Suffix}}Request(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { return nil, err } diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl new file mode 100644 index 0000000000..9a6bc11c60 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -0,0 +1,124 @@ +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := r.ParseMultipartForm(32 << 20); err != nil { + http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest) + return + } + + {{$nameTag := .NameTag}} + {{range .Schema.Properties -}} + {{if eq .GoTypeDef "*multipart.FileHeader"}} + if fhs := r.MultipartForm.File["{{.JsonFieldName}}"]; len(fhs) > 0 { + body.{{.GoFieldName}} = fhs[0] + } + {{else}} + body.{{.GoFieldName}} = r.FormValue("{{.JsonFieldName}}") + {{end}} + {{end}}{{/* range .Schema.Properties */}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body: " + err.Error(), http.StatusBadRequest) + return + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + w.Header().Set("Content-Type", "{{.ContentType}}") + w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{if eq .NameTag "JSON" -}} + writeJSON(w, v) + {{else if eq .NameTag "Binary" -}} + writeRaw(w, v) + {{else if eq .NameTag "Text" -}} + writeRaw(w, ([]byte)(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } + } +{{end}} + +func writeJSON(w http.ResponseWriter, v interface{}) { + if err := json.NewEncoder(w).Encode(v); err != nil { + fmt.Fprintln(w, err) + } +} + +func writeRaw(w http.ResponseWriter, b []byte) { + if _, err := w.Write(b); err != nil { + fmt.Fprintln(w, err) + } +} diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl new file mode 100644 index 0000000000..c7c6d0110f --- /dev/null +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -0,0 +1,97 @@ +type StrictHandlerFunc func(ctx echo.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := ctx.Bind(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := ctx.Bind(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx echo.Context, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx.Request().Context(), request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(ctx, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "JSON" -}} + ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) + {{else if eq .NameTag "Binary" -}} + ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + {{else if eq .NameTag "Text" -}} + ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil + } +{{end}} diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl new file mode 100644 index 0000000000..3aef0f4dad --- /dev/null +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -0,0 +1,99 @@ +type StrictHandlerFunc func(ctx *gin.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(ctx.Request.Body) + if err != nil { + ctx.Error(err) + return + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx *gin.Context, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(ctx, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + ctx.Header("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "JSON" -}} + ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) + {{else if eq .NameTag "Binary" -}} + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + {{else if eq .NameTag "Text" -}} + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + ctx.Header("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } + } +{{end}} diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl new file mode 100644 index 0000000000..30e152f8d4 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -0,0 +1,76 @@ +{{range .}} + {{$opid := .OperationId -}} + type {{$opid | ucFirst}}RequestObject struct { + {{range .PathParams -}} + {{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}} + {{end -}} + {{if .RequiresParamObject -}} + Params {{$opid}}Params + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies)}} + {{range .Bodies -}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body *{{$opid}}{{.NameTag}}RequestBody + {{end -}} + } + + {{range .Responses}} + {{$statusCode := .StatusCode}} + {{$hasHeaders := ne 0 (len .Headers)}} + {{$fixedStatusCode := .HasFixedStatusCode}} + + {{if $hasHeaders}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end}} + } + {{end}} + + {{range .Contents}} + {{if and (not $hasHeaders) ($fixedStatusCode) -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}} + + {{if not (and (opts.AliasTypes) (.Schema.IsRef))}} + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(({{.Schema.GoType}})(t)) + } + {{end}} + {{else -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response struct { + Body {{.Schema.TypeDecl}} + {{if $hasHeaders -}} + Headers {{$opid}}{{$statusCode}}ResponseHeaders + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} + {{end}} + + {{if eq 0 (len .Contents) -}} + type {{$opid}}{{$statusCode}}Response struct { + {{if $hasHeaders -}} + Headers {{$opid}}{{$statusCode}}ResponseHeaders + {{end}} + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + {{end}} + {{end}} +{{end}} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{$opid := .OperationId -}} +{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) interface{} +{{end}}{{/* range . */ -}} +} diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index a7211f9b28..980c2392dc 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -605,7 +605,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge {{range .Bodies}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { - req, err := New{{$opid}}{{.Suffix}}Request(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { return nil, err } @@ -1316,6 +1316,406 @@ type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.Ty {{end}} {{end}} {{end}} +`, + "strict-chi.tmpl": `type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := r.ParseMultipartForm(32 << 20); err != nil { + http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest) + return + } + + {{$nameTag := .NameTag}} + {{range .Schema.Properties -}} + {{if eq .GoTypeDef "*multipart.FileHeader"}} + if fhs := r.MultipartForm.File["{{.JsonFieldName}}"]; len(fhs) > 0 { + body.{{.GoFieldName}} = fhs[0] + } + {{else}} + body.{{.GoFieldName}} = r.FormValue("{{.JsonFieldName}}") + {{end}} + {{end}}{{/* range .Schema.Properties */}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body: " + err.Error(), http.StatusBadRequest) + return + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + w.Header().Set("Content-Type", "{{.ContentType}}") + w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{if eq .NameTag "JSON" -}} + writeJSON(w, v) + {{else if eq .NameTag "Binary" -}} + writeRaw(w, v) + {{else if eq .NameTag "Text" -}} + writeRaw(w, ([]byte)(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } + } +{{end}} + +func writeJSON(w http.ResponseWriter, v interface{}) { + if err := json.NewEncoder(w).Encode(v); err != nil { + fmt.Fprintln(w, err) + } +} + +func writeRaw(w http.ResponseWriter, b []byte) { + if _, err := w.Write(b); err != nil { + fmt.Fprintln(w, err) + } +} +`, + "strict-echo.tmpl": `type StrictHandlerFunc func(ctx echo.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := ctx.Bind(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := ctx.Bind(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx echo.Context, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx.Request().Context(), request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(ctx, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "JSON" -}} + ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) + {{else if eq .NameTag "Binary" -}} + ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + {{else if eq .NameTag "Text" -}} + ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil + } +{{end}} +`, + "strict-gin.tmpl": `type StrictHandlerFunc func(ctx *gin.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + {{$varName := .GoVariableName -}} + request.{{$varName | ucFirst}} = {{$varName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{$multipleBodies := gt 1 (len .Bodies) -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} + var body {{$opid}}{{.NameTag}}RequestBody + {{if eq .NameTag "JSON" -}} + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Text" -}} + data, err := ioutil.ReadAll(ctx.Request.Body) + if err != nil { + ctx.Error(err) + return + } + body = {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end -}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx *gin.Context, request interface{}) interface{}{ + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response := handler(ctx, request) + + switch v := response.(type) { + {{range .Responses -}} + {{$statusCode := .StatusCode -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$headers := .Headers -}} + {{range .Contents -}} + case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + {{range $headers -}} + ctx.Header("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "JSON" -}} + ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) + {{else if eq .NameTag "Binary" -}} + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + {{else if eq .NameTag "Text" -}} + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{end}}{{/* range .Contents */ -}} + {{if eq 0 (len .Contents) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + ctx.Header("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{end}}{{/* if eq 0 (len .Contents) */ -}} + {{end}}{{/* range .Responses */ -}} + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } + } +{{end}} +`, + "strict-interface.tmpl": `{{range .}} + {{$opid := .OperationId -}} + type {{$opid | ucFirst}}RequestObject struct { + {{range .PathParams -}} + {{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}} + {{end -}} + {{if .RequiresParamObject -}} + Params {{$opid}}Params + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies)}} + {{range .Bodies -}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body *{{$opid}}{{.NameTag}}RequestBody + {{end -}} + } + + {{range .Responses}} + {{$statusCode := .StatusCode}} + {{$hasHeaders := ne 0 (len .Headers)}} + {{$fixedStatusCode := .HasFixedStatusCode}} + + {{if $hasHeaders}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end}} + } + {{end}} + + {{range .Contents}} + {{if and (not $hasHeaders) ($fixedStatusCode) -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}} + + {{if not (and (opts.AliasTypes) (.Schema.IsRef))}} + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(({{.Schema.GoType}})(t)) + } + {{end}} + {{else -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response struct { + Body {{.Schema.TypeDecl}} + {{if $hasHeaders -}} + Headers {{$opid}}{{$statusCode}}ResponseHeaders + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} + {{end}} + + {{if eq 0 (len .Contents) -}} + type {{$opid}}{{$statusCode}}Response struct { + {{if $hasHeaders -}} + Headers {{$opid}}{{$statusCode}}ResponseHeaders + {{end}} + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + {{end}} + {{end}} +{{end}} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{$opid := .OperationId -}} +{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) interface{} +{{end}}{{/* range . */ -}} +} `, "typedef.tmpl": `{{range .Types}} {{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }} From ca52c0240c7f790b7db3934961433dad64a9237c Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sat, 27 Nov 2021 03:18:43 +0300 Subject: [PATCH 02/19] Support any content type by using io.Reader --- internal/test/client/client.gen.go | 83 -------------- pkg/codegen/operations.go | 41 ++++++- .../templates/client-with-responses.tmpl | 6 +- pkg/codegen/templates/client.tmpl | 6 ++ pkg/codegen/templates/request-bodies.tmpl | 2 + pkg/codegen/templates/strict/strict-chi.tmpl | 22 +++- pkg/codegen/templates/strict/strict-echo.tmpl | 24 +++-- pkg/codegen/templates/strict/strict-gin.tmpl | 15 ++- .../templates/strict/strict-interface.tmpl | 26 +++-- pkg/codegen/templates/templates.gen.go | 102 ++++++++++++++---- 10 files changed, 197 insertions(+), 130 deletions(-) diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index 7ecf2b6fe1..abea3b4db2 100644 --- a/internal/test/client/client.gen.go +++ b/internal/test/client/client.gen.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "io/ioutil" - "mime/multipart" "net/http" "net/url" "path" @@ -35,27 +34,15 @@ type SchemaObject struct { // PostBothJSONBody defines parameters for PostBoth. type PostBothJSONBody SchemaObject -// PostBothBinaryBody defines parameters for PostBoth. -type PostBothBinaryBody *multipart.FileHeader - // PostJsonJSONBody defines parameters for PostJson. type PostJsonJSONBody SchemaObject -// PostOtherBinaryBody defines parameters for PostOther. -type PostOtherBinaryBody *multipart.FileHeader - // PostBothJSONRequestBody defines body for PostBoth for application/json ContentType. type PostBothJSONRequestBody PostBothJSONBody -// PostBothBinaryRequestBody defines body for PostBoth for application/json ContentType. -type PostBothBinaryRequestBody PostBothBinaryBody - // PostJsonJSONRequestBody defines body for PostJson for application/json ContentType. type PostJsonJSONRequestBody PostJsonJSONBody -// PostOtherBinaryRequestBody defines body for PostOther for application/json ContentType. -type PostOtherBinaryRequestBody PostOtherBinaryBody - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -134,8 +121,6 @@ type ClientInterface interface { PostBoth(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - PostBothWithBinaryBody(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetBoth request GetBoth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -150,8 +135,6 @@ type ClientInterface interface { // PostOther request with any body PostOtherWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostOtherWithBinaryBody(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetOther request GetOther(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -183,18 +166,6 @@ func (c *Client) PostBoth(ctx context.Context, body PostBothJSONRequestBody, req return c.Client.Do(req) } -func (c *Client) PostBothWithBinaryBody(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostBothRequestWithBinaryBody(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) GetBoth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetBothRequest(c.Server) if err != nil { @@ -255,18 +226,6 @@ func (c *Client) PostOtherWithBody(ctx context.Context, contentType string, body return c.Client.Do(req) } -func (c *Client) PostOtherWithBinaryBody(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostOtherRequestWithBinaryBody(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) GetOther(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetOtherRequest(c.Server) if err != nil { @@ -302,17 +261,6 @@ func NewPostBothRequest(server string, body PostBothJSONRequestBody) (*http.Requ return NewPostBothRequestWithBody(server, "application/json", bodyReader) } -// NewPostBothRequestWithBinaryBody calls the generic PostBoth builder with application/octet-stream body -func NewPostBothRequestWithBinaryBody(server string, body PostBothBinaryRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPostBothRequestWithBody(server, "application/octet-stream", bodyReader) -} - // NewPostBothRequestWithBody generates requests for PostBoth with any type of body func NewPostBothRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -436,17 +384,6 @@ func NewGetJsonRequest(server string) (*http.Request, error) { return req, nil } -// NewPostOtherRequestWithBinaryBody calls the generic PostOther builder with application/octet-stream body -func NewPostOtherRequestWithBinaryBody(server string, body PostOtherBinaryRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPostOtherRequestWithBody(server, "application/octet-stream", bodyReader) -} - // NewPostOtherRequestWithBody generates requests for PostOther with any type of body func NewPostOtherRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -578,8 +515,6 @@ type ClientWithResponsesInterface interface { PostBothWithResponse(ctx context.Context, body PostBothJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) - PostBothWithBinaryBodyWithResponse(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) - // GetBoth request GetBothWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetBothResponse, error) @@ -594,8 +529,6 @@ type ClientWithResponsesInterface interface { // PostOther request with any body PostOtherWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) - PostOtherWithBinaryBodyWithResponse(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) - // GetOther request GetOtherWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOtherResponse, error) @@ -767,14 +700,6 @@ func (c *ClientWithResponses) PostBothWithResponse(ctx context.Context, body Pos return ParsePostBothResponse(rsp) } -func (c *ClientWithResponses) PostBothWithBinaryBodyWithResponse(ctx context.Context, body PostBothBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostBothResponse, error) { - rsp, err := c.PostBothWithBinaryBody(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostBothResponse(rsp) -} - // GetBothWithResponse request returning *GetBothResponse func (c *ClientWithResponses) GetBothWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetBothResponse, error) { rsp, err := c.GetBoth(ctx, reqEditors...) @@ -819,14 +744,6 @@ func (c *ClientWithResponses) PostOtherWithBodyWithResponse(ctx context.Context, return ParsePostOtherResponse(rsp) } -func (c *ClientWithResponses) PostOtherWithBinaryBodyWithResponse(ctx context.Context, body PostOtherBinaryRequestBody, reqEditors ...RequestEditorFn) (*PostOtherResponse, error) { - rsp, err := c.PostOtherWithBinaryBody(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostOtherResponse(rsp) -} - // GetOtherWithResponse request returning *GetOtherResponse func (c *ClientWithResponses) GetOtherWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOtherResponse, error) { rsp, err := c.GetOther(ctx, reqEditors...) diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index a4ea0113d9..cdf388d804 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -317,6 +317,15 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini return tds, nil } +func (o OperationDefinition) HasMaskedRequestContentTypes() bool { + for _, body := range o.Bodies { + if !body.IsFixedContentType() { + return true + } + } + return false +} + // This describes a request body type RequestBodyDefinition struct { // Is this body required, or optional? @@ -363,6 +372,16 @@ func (r RequestBodyDefinition) Suffix() string { return "With" + r.NameTag + "Body" } +// Returns true if we support this type of content type. Otherwise io.Reader will be generated +func (r RequestBodyDefinition) IsSupported() bool { + return r.NameTag != "" +} + +// Returns true if content type has fixed content type, i.e. contains no "*" symbol +func (r RequestBodyDefinition) IsFixedContentType() bool { + return !strings.Contains(r.ContentType, "*") +} + type ResponseDefinition struct { StatusCode string Description string @@ -395,6 +414,15 @@ func (r ResponseContentDefinition) TypeDef(opID string, statusCode int) *TypeDef } } +func (r ResponseContentDefinition) IsSupported() bool { + return r.NameTag != "" +} + +// Returns true if content type has fixed content type, i.e. contains no "*" symbol +func (r ResponseContentDefinition) HasFixedContentType() bool { + return !strings.Contains(r.ContentType, "*") +} + type ResponseHeaderDefinition struct { Name string GoName string @@ -562,9 +590,12 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody tag = "Formdata" case "text/plain": tag = "Text" - case "application/octet-stream": - tag = "Binary" default: + bd := RequestBodyDefinition{ + Required: body.Required, + ContentType: contentType, + } + bodyDefinitions = append(bodyDefinitions, bd) continue } @@ -627,9 +658,11 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response tag = "JSON" case "text/plain": tag = "Text" - case "application/octet-stream": - tag = "Binary" default: + rcd := ResponseContentDefinition{ + ContentType: contentType, + } + responseContentDefinitions = append(responseContentDefinitions, rcd) continue } diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 19722ec005..7eac5e7456 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -34,7 +34,9 @@ type ClientWithResponsesInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{range .Bodies}} - {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) + {{if .IsSupported -}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) + {{end -}} {{end}}{{/* range .Bodies */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -83,6 +85,7 @@ func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithRespons {{$pathParams := .PathParams -}} {{$bodyRequired := .BodyRequired -}} {{range .Bodies}} +{{if .IsSupported -}} func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) { rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) if err != nil { @@ -91,6 +94,7 @@ func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Conte return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) } {{end}} +{{end}} {{end}}{{/* operations */}} diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index 7a22eb632b..82452155aa 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -78,7 +78,9 @@ type ClientInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {{range .Bodies}} + {{if .IsSupported -}} {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) + {{end -}} {{end}}{{/* range .Bodies */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -103,6 +105,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge } {{range .Bodies}} +{{if .IsSupported -}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { @@ -114,6 +117,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar } return c.Client.Do(req) } +{{end -}}{{/* if .IsSupported */}} {{end}}{{/* range .Bodies */}} {{end}} @@ -125,6 +129,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar {{$opid := .OperationId -}} {{range .Bodies}} +{{if .IsSupported -}} // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -135,6 +140,7 @@ func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{i bodyReader = bytes.NewReader(buf) return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader) } +{{end -}} {{end}} // New{{$opid}}Request{{if .HasBody}}WithBody{{end}} generates requests for {{$opid}}{{if .HasBody}} with any type of body{{end}} diff --git a/pkg/codegen/templates/request-bodies.tmpl b/pkg/codegen/templates/request-bodies.tmpl index 313dd11bf4..473f7030d4 100644 --- a/pkg/codegen/templates/request-bodies.tmpl +++ b/pkg/codegen/templates/request-bodies.tmpl @@ -1,8 +1,10 @@ {{range .}}{{$opid := .OperationId}} {{range .Bodies}} +{{if .IsSupported -}} {{with .TypeDef $opid}} // {{.TypeName}} defines body for {{$opid}} for application/json ContentType. type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} {{end}} {{end}} {{end}} +{{end}} \ No newline at end of file diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl index 9a6bc11c60..5dd891a6b3 100644 --- a/pkg/codegen/templates/strict/strict-chi.tmpl +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -26,10 +26,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = r.Header.Get("Content-Type") + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) @@ -61,6 +65,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -84,14 +90,22 @@ type strictHandler struct { {{range $headers -}} w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} - w.Header().Set("Content-Type", "{{.ContentType}}") + w.Header().Set("Content-Type", {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}) + {{if not .IsSupported -}} + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + {{end -}} w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) {{if eq .NameTag "JSON" -}} writeJSON(w, v) - {{else if eq .NameTag "Binary" -}} - writeRaw(w, v) {{else if eq .NameTag "Text" -}} writeRaw(w, ([]byte)(v)) + {{else -}} + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl index c7c6d0110f..29bd9dff12 100644 --- a/pkg/codegen/templates/strict/strict-echo.tmpl +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -26,10 +26,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.Request().Header.Get("Content-Type") + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := ctx.Bind(&body); err != nil { return err @@ -47,6 +51,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -71,11 +77,17 @@ type strictHandler struct { ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} {{if eq .NameTag "JSON" -}} - ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) - {{else if eq .NameTag "Binary" -}} - ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + return ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) {{else if eq .NameTag "Text" -}} - ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + return ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(v)) + {{else -}} + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}, v.Body) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} @@ -83,7 +95,7 @@ type strictHandler struct { {{range $headers -}} ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} - ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + return ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) {{end}}{{/* if eq 0 (len .Contents) */ -}} {{end}}{{/* range .Responses */ -}} case error: diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl index 3aef0f4dad..5e74f9e832 100644 --- a/pkg/codegen/templates/strict/strict-gin.tmpl +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -26,10 +26,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.ContentType() + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := ctx.Bind(&body); err != nil { ctx.Error(err) @@ -50,6 +54,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -75,10 +81,13 @@ type strictHandler struct { {{end -}} {{if eq .NameTag "JSON" -}} ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) - {{else if eq .NameTag "Binary" -}} - ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) {{else if eq .NameTag "Text" -}} ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{else -}} + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v.ContentLength, {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}, v.Body, nil) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 30e152f8d4..6e0d72da7b 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -7,9 +7,12 @@ {{if .RequiresParamObject -}} Params {{$opid}}Params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + ContentType string + {{end -}} {{$multipleBodies := gt 1 (len .Bodies)}} {{range .Bodies -}} - {{if $multipleBodies}}{{.NameTag}}{{end}}Body *{{$opid}}{{.NameTag}}RequestBody + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if .IsSupported}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} {{end -}} } @@ -27,8 +30,8 @@ {{end}} {{range .Contents}} - {{if and (not $hasHeaders) ($fixedStatusCode) -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}} + {{if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if .IsSupported}}{{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if not (and (opts.AliasTypes) (.Schema.IsRef))}} func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { @@ -37,7 +40,7 @@ {{end}} {{else -}} type {{$opid}}{{$statusCode}}{{.NameTag}}Response struct { - Body {{.Schema.TypeDecl}} + Body {{if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} Headers {{$opid}}{{$statusCode}}ResponseHeaders {{end -}} @@ -45,11 +48,20 @@ {{if not $fixedStatusCode -}} StatusCode int {{end -}} - } - func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Body) + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} } + {{if .IsSupported}} + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} {{end}} {{end}} diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index 980c2392dc..332bd38344 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -419,7 +419,9 @@ type ClientWithResponsesInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{range .Bodies}} - {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) + {{if .IsSupported -}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) + {{end -}} {{end}}{{/* range .Bodies */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -468,6 +470,7 @@ func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithRespons {{$pathParams := .PathParams -}} {{$bodyRequired := .BodyRequired -}} {{range .Bodies}} +{{if .IsSupported -}} func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) { rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) if err != nil { @@ -476,6 +479,7 @@ func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Conte return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) } {{end}} +{{end}} {{end}}{{/* operations */}} @@ -579,7 +583,9 @@ type ClientInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {{range .Bodies}} + {{if .IsSupported -}} {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) + {{end -}} {{end}}{{/* range .Bodies */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -604,6 +610,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge } {{range .Bodies}} +{{if .IsSupported -}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { @@ -615,6 +622,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar } return c.Client.Do(req) } +{{end -}}{{/* if .IsSupported */}} {{end}}{{/* range .Bodies */}} {{end}} @@ -626,6 +634,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar {{$opid := .OperationId -}} {{range .Bodies}} +{{if .IsSupported -}} // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -636,6 +645,7 @@ func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{i bodyReader = bytes.NewReader(buf) return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader) } +{{end -}} {{end}} // New{{$opid}}Request{{if .HasBody}}WithBody{{end}} generates requests for {{$opid}}{{if .HasBody}} with any type of body{{end}} @@ -1310,13 +1320,14 @@ type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.Ty `, "request-bodies.tmpl": `{{range .}}{{$opid := .OperationId}} {{range .Bodies}} +{{if .IsSupported -}} {{with .TypeDef $opid}} // {{.TypeName}} defines body for {{$opid}} for application/json ContentType. type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} {{end}} {{end}} {{end}} -`, +{{end}}`, "strict-chi.tmpl": `type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc @@ -1345,10 +1356,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = r.Header.Get("Content-Type") + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) @@ -1380,6 +1395,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -1403,14 +1420,22 @@ type strictHandler struct { {{range $headers -}} w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} - w.Header().Set("Content-Type", "{{.ContentType}}") + w.Header().Set("Content-Type", {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}) + {{if not .IsSupported -}} + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + {{end -}} w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) {{if eq .NameTag "JSON" -}} writeJSON(w, v) - {{else if eq .NameTag "Binary" -}} - writeRaw(w, v) {{else if eq .NameTag "Text" -}} writeRaw(w, ([]byte)(v)) + {{else -}} + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} @@ -1470,10 +1495,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.Request().Header.Get("Content-Type") + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := ctx.Bind(&body); err != nil { return err @@ -1491,6 +1520,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -1515,11 +1546,17 @@ type strictHandler struct { ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} {{if eq .NameTag "JSON" -}} - ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) - {{else if eq .NameTag "Binary" -}} - ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) + return ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) {{else if eq .NameTag "Text" -}} - ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + return ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(v)) + {{else -}} + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}, v.Body) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} @@ -1527,7 +1564,7 @@ type strictHandler struct { {{range $headers -}} ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} - ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + return ctx.NoContent({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) {{end}}{{/* if eq 0 (len .Contents) */ -}} {{end}}{{/* range .Responses */ -}} case error: @@ -1568,10 +1605,14 @@ type strictHandler struct { request.Params = params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.ContentType() + {{end -}} + {{$multipleBodies := gt 1 (len .Bodies) -}} {{range .Bodies -}} {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} - var body {{$opid}}{{.NameTag}}RequestBody + {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} {{if eq .NameTag "JSON" -}} if err := ctx.Bind(&body); err != nil { ctx.Error(err) @@ -1592,6 +1633,8 @@ type strictHandler struct { } body = {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end -}} {{end}}{{/* range .Bodies */}} @@ -1617,10 +1660,13 @@ type strictHandler struct { {{end -}} {{if eq .NameTag "JSON" -}} ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) - {{else if eq .NameTag "Binary" -}} - ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, v) {{else if eq .NameTag "Text" -}} ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + {{else -}} + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v.ContentLength, {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}, v.Body, nil) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{end}}{{/* range .Contents */ -}} {{if eq 0 (len .Contents) -}} @@ -1649,9 +1695,12 @@ type strictHandler struct { {{if .RequiresParamObject -}} Params {{$opid}}Params {{end -}} + {{ if .HasMaskedRequestContentTypes -}} + ContentType string + {{end -}} {{$multipleBodies := gt 1 (len .Bodies)}} {{range .Bodies -}} - {{if $multipleBodies}}{{.NameTag}}{{end}}Body *{{$opid}}{{.NameTag}}RequestBody + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if .IsSupported}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} {{end -}} } @@ -1669,8 +1718,8 @@ type strictHandler struct { {{end}} {{range .Contents}} - {{if and (not $hasHeaders) ($fixedStatusCode) -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}} + {{if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if .IsSupported}}{{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if not (and (opts.AliasTypes) (.Schema.IsRef))}} func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { @@ -1679,7 +1728,7 @@ type strictHandler struct { {{end}} {{else -}} type {{$opid}}{{$statusCode}}{{.NameTag}}Response struct { - Body {{.Schema.TypeDecl}} + Body {{if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} Headers {{$opid}}{{$statusCode}}ResponseHeaders {{end -}} @@ -1687,11 +1736,20 @@ type strictHandler struct { {{if not $fixedStatusCode -}} StatusCode int {{end -}} - } - func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Body) + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} } + {{if .IsSupported}} + func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} {{end}} {{end}} From b26bf81e2d099c95a0bfc38ea0583d19f42bfb27 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Mon, 13 Dec 2021 13:40:47 +0300 Subject: [PATCH 03/19] removed generating marshalling code from client for content types other than JSON --- internal/test/components/components.gen.go | 35 ------------------- pkg/codegen/operations.go | 9 +++-- .../templates/client-with-responses.tmpl | 4 +-- pkg/codegen/templates/client.tmpl | 6 ++-- pkg/codegen/templates/templates.gen.go | 10 +++--- 5 files changed, 17 insertions(+), 47 deletions(-) diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index c7295d36fc..a25f55114d 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -798,8 +798,6 @@ type ClientInterface interface { // EnsureEverythingIsReferenced request with any body EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // ParamsWithAddProps request @@ -823,18 +821,6 @@ func (c *Client) EnsureEverythingIsReferencedWithBody(ctx context.Context, conte return c.Client.Do(req) } -func (c *Client) EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureEverythingIsReferencedRequestWithTextBody(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewEnsureEverythingIsReferencedRequest(c.Server, body) if err != nil { @@ -883,17 +869,6 @@ func (c *Client) BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSON return c.Client.Do(req) } -// NewEnsureEverythingIsReferencedRequestWithTextBody calls the generic EnsureEverythingIsReferenced builder with text/plain body -func NewEnsureEverythingIsReferencedRequestWithTextBody(server string, body EnsureEverythingIsReferencedTextRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewEnsureEverythingIsReferencedRequestWithBody(server, "text/plain", bodyReader) -} - // NewEnsureEverythingIsReferencedRequest calls the generic EnsureEverythingIsReferenced builder with application/json body func NewEnsureEverythingIsReferencedRequest(server string, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -1075,8 +1050,6 @@ type ClientWithResponsesInterface interface { // EnsureEverythingIsReferenced request with any body EnsureEverythingIsReferencedWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) // ParamsWithAddProps request @@ -1180,14 +1153,6 @@ func (c *ClientWithResponses) EnsureEverythingIsReferencedWithBodyWithResponse(c return ParseEnsureEverythingIsReferencedResponse(rsp) } -func (c *ClientWithResponses) EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { - rsp, err := c.EnsureEverythingIsReferencedWithTextBody(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseEnsureEverythingIsReferencedResponse(rsp) -} - func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { rsp, err := c.EnsureEverythingIsReferenced(ctx, body, reqEditors...) if err != nil { diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index cdf388d804..8c4c407f87 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -372,7 +372,12 @@ func (r RequestBodyDefinition) Suffix() string { return "With" + r.NameTag + "Body" } -// Returns true if we support this type of content type. Otherwise io.Reader will be generated +// Returns true if we support this content type for client. Otherwise only generic method will ge generated +func (r RequestBodyDefinition) IsSupportedByClient() bool { + return r.NameTag == "JSON" +} + +// Returns true if we support this content type for server. Otherwise io.Reader will be generated func (r RequestBodyDefinition) IsSupported() bool { return r.NameTag != "" } @@ -586,7 +591,7 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody case "application/json": tag = "JSON" defaultBody = true - case "multipart/form-data": + case "multipart/form-data", "application/x-www-form-urlencoded": tag = "Formdata" case "text/plain": tag = "Text" diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 7eac5e7456..46ec0b3adc 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -34,7 +34,7 @@ type ClientWithResponsesInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{range .Bodies}} - {{if .IsSupported -}} + {{if .IsSupportedByClient -}} {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{end -}} {{end}}{{/* range .Bodies */}} @@ -85,7 +85,7 @@ func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithRespons {{$pathParams := .PathParams -}} {{$bodyRequired := .BodyRequired -}} {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) { rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) if err != nil { diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index 82452155aa..648532f08c 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -78,7 +78,7 @@ type ClientInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {{range .Bodies}} - {{if .IsSupported -}} + {{if .IsSupportedByClient -}} {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) {{end -}} {{end}}{{/* range .Bodies */}} @@ -105,7 +105,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge } {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { @@ -129,7 +129,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar {{$opid := .OperationId -}} {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index 332bd38344..be9a1e2238 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -419,7 +419,7 @@ type ClientWithResponsesInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{range .Bodies}} - {{if .IsSupported -}} + {{if .IsSupportedByClient -}} {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {{end -}} {{end}}{{/* range .Bodies */}} @@ -470,7 +470,7 @@ func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithRespons {{$pathParams := .PathParams -}} {{$bodyRequired := .BodyRequired -}} {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) { rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) if err != nil { @@ -583,7 +583,7 @@ type ClientInterface interface { // {{$opid}} request{{if .HasBody}} with any body{{end}} {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {{range .Bodies}} - {{if .IsSupported -}} + {{if .IsSupportedByClient -}} {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) {{end -}} {{end}}{{/* range .Bodies */}} @@ -610,7 +610,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge } {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { @@ -634,7 +634,7 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar {{$opid := .OperationId -}} {{range .Bodies}} -{{if .IsSupported -}} +{{if .IsSupportedByClient -}} // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader From 063323ba245ce8c43c54e6382916e1bc91d166a9 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Tue, 14 Dec 2021 23:03:56 +0300 Subject: [PATCH 04/19] Multipart and formdata are now passed as forms without any binding --- pkg/codegen/operations.go | 4 +- pkg/codegen/templates/strict/strict-chi.tmpl | 27 +++---- pkg/codegen/templates/strict/strict-echo.tmpl | 21 ++++-- pkg/codegen/templates/strict/strict-gin.tmpl | 19 +++-- .../templates/strict/strict-interface.tmpl | 6 +- pkg/codegen/templates/templates.gen.go | 73 +++++++++++-------- 6 files changed, 85 insertions(+), 65 deletions(-) diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 8c4c407f87..79ea8c2410 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -591,7 +591,9 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody case "application/json": tag = "JSON" defaultBody = true - case "multipart/form-data", "application/x-www-form-urlencoded": + case "multipart/form-data": + tag = "Multipart" + case "application/x-www-form-urlencoded": tag = "Formdata" case "text/plain": tag = "Text" diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl index 5dd891a6b3..6584156201 100644 --- a/pkg/codegen/templates/strict/strict-chi.tmpl +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -30,33 +30,28 @@ type strictHandler struct { request.ContentType = r.Header.Get("Content-Type") {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) return } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} + if err := r.ParseForm(); err != nil { + http.Error(w, "can't decode formdata: " + err.Error(), http.StatusBadRequest) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &r.Form + {{else if eq .NameTag "Multipart" -}} if err := r.ParseMultipartForm(32 << 20); err != nil { http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest) return } - - {{$nameTag := .NameTag}} - {{range .Schema.Properties -}} - {{if eq .GoTypeDef "*multipart.FileHeader"}} - if fhs := r.MultipartForm.File["{{.JsonFieldName}}"]; len(fhs) > 0 { - body.{{.GoFieldName}} = fhs[0] - } - {{else}} - body.{{.GoFieldName}} = r.FormValue("{{.JsonFieldName}}") - {{end}} - {{end}}{{/* range .Schema.Properties */}} - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.MultipartForm {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(r.Body) if err != nil { @@ -68,7 +63,7 @@ type strictHandler struct { {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{}{ diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl index 29bd9dff12..b2976c9e77 100644 --- a/pkg/codegen/templates/strict/strict-echo.tmpl +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -30,31 +30,38 @@ type strictHandler struct { request.ContentType = ctx.Request().Header.Get("Content-Type") {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := ctx.Bind(&body); err != nil { return err } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} - if err := ctx.Bind(&body); err != nil { + if body, err := ctx.FormParams(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + } else { + return err + } + {{else if eq .NameTag "Multipart" -}} + if body, err := ctx.MultipartForm(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body + } else { return err } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(ctx.Request().Body) if err != nil { return err } - body = {{$opid}}{{.NameTag}}RequestBody(data) + body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx echo.Context, request interface{}) interface{}{ diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl index 5e74f9e832..e335286abb 100644 --- a/pkg/codegen/templates/strict/strict-gin.tmpl +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -30,22 +30,29 @@ type strictHandler struct { request.ContentType = ctx.ContentType() {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := ctx.Bind(&body); err != nil { ctx.Error(err) return } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} - if err := ctx.Bind(&body); err != nil { + if err := ctx.Request.ParseForm(); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &ctx.Request.Form + {{else if eq .NameTag "Multipart" -}} + if body, err := ctx.MultipartForm(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body + } else { ctx.Error(err) return } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(ctx.Request.Body) if err != nil { @@ -57,7 +64,7 @@ type strictHandler struct { {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx *gin.Context, request interface{}) interface{}{ diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 6e0d72da7b..fd22d67afc 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -7,12 +7,12 @@ {{if .RequiresParamObject -}} Params {{$opid}}Params {{end -}} - {{ if .HasMaskedRequestContentTypes -}} + {{if .HasMaskedRequestContentTypes -}} ContentType string {{end -}} - {{$multipleBodies := gt 1 (len .Bodies)}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if .IsSupported}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "JSON"}}*{{$opid}}{{.NameTag}}RequestBody{{else if eq .NameTag "Multipart"}}*multipart.Form{{else if eq .NameTag "Formdata"}}*url.Values{{else}}io.Reader{{end}} {{end -}} } diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index be9a1e2238..ed835a5f8a 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -1360,33 +1360,28 @@ type strictHandler struct { request.ContentType = r.Header.Get("Content-Type") {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest) return } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} + if err := r.ParseForm(); err != nil { + http.Error(w, "can't decode formdata: " + err.Error(), http.StatusBadRequest) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &r.Form + {{else if eq .NameTag "Multipart" -}} if err := r.ParseMultipartForm(32 << 20); err != nil { http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest) return } - - {{$nameTag := .NameTag}} - {{range .Schema.Properties -}} - {{if eq .GoTypeDef "*multipart.FileHeader"}} - if fhs := r.MultipartForm.File["{{.JsonFieldName}}"]; len(fhs) > 0 { - body.{{.GoFieldName}} = fhs[0] - } - {{else}} - body.{{.GoFieldName}} = r.FormValue("{{.JsonFieldName}}") - {{end}} - {{end}}{{/* range .Schema.Properties */}} - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.MultipartForm {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(r.Body) if err != nil { @@ -1398,7 +1393,7 @@ type strictHandler struct { {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{}{ @@ -1499,31 +1494,38 @@ type strictHandler struct { request.ContentType = ctx.Request().Header.Get("Content-Type") {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := ctx.Bind(&body); err != nil { return err } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} - if err := ctx.Bind(&body); err != nil { + if body, err := ctx.FormParams(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + } else { + return err + } + {{else if eq .NameTag "Multipart" -}} + if body, err := ctx.MultipartForm(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body + } else { return err } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(ctx.Request().Body) if err != nil { return err } - body = {{$opid}}{{.NameTag}}RequestBody(data) + body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx echo.Context, request interface{}) interface{}{ @@ -1609,22 +1611,29 @@ type strictHandler struct { request.ContentType = ctx.ContentType() {{end -}} - {{$multipleBodies := gt 1 (len .Bodies) -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end -}} - {{if .IsSupported}}var body {{$opid}}{{.NameTag}}RequestBody{{end}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody if err := ctx.Bind(&body); err != nil { ctx.Error(err) return } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} - if err := ctx.Bind(&body); err != nil { + if err := ctx.Request.ParseForm(); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &ctx.Request.Form + {{else if eq .NameTag "Multipart" -}} + if body, err := ctx.MultipartForm(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body + } else { ctx.Error(err) return } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(ctx.Request.Body) if err != nil { @@ -1636,7 +1645,7 @@ type strictHandler struct { {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body {{end}}{{/* if eq .NameTag "JSON" */ -}} - {{if $multipleBodies}}}{{end -}} + {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} handler := func(ctx *gin.Context, request interface{}) interface{}{ @@ -1695,12 +1704,12 @@ type strictHandler struct { {{if .RequiresParamObject -}} Params {{$opid}}Params {{end -}} - {{ if .HasMaskedRequestContentTypes -}} + {{if .HasMaskedRequestContentTypes -}} ContentType string {{end -}} - {{$multipleBodies := gt 1 (len .Bodies)}} + {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if .IsSupported}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "JSON"}}*{{$opid}}{{.NameTag}}RequestBody{{else if eq .NameTag "Multipart"}}*multipart.Form{{else if eq .NameTag "Formdata"}}*url.Values{{else}}io.Reader{{end}} {{end -}} } From 97b84b8f28f15d171b89cbf20de2ea5eb1f22a1d Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Tue, 14 Dec 2021 23:05:12 +0300 Subject: [PATCH 05/19] run make generate to fix the build --- internal/test/components/components.gen.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index a25f55114d..7fb9f6d96d 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -117,12 +117,12 @@ type BodyWithAddPropsJSONBody_Inner struct { AdditionalProperties map[string]int `json:"-"` } -// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. -type EnsureEverythingIsReferencedTextRequestBody RequestBody - // EnsureEverythingIsReferencedJSONRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. type EnsureEverythingIsReferencedJSONRequestBody RequestBody +// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. +type EnsureEverythingIsReferencedTextRequestBody RequestBody + // BodyWithAddPropsJSONRequestBody defines body for BodyWithAddProps for application/json ContentType. type BodyWithAddPropsJSONRequestBody BodyWithAddPropsJSONBody From e6633536274fd4987fb97e3f56251fdc49202ce0 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Wed, 15 Dec 2021 09:45:32 +0300 Subject: [PATCH 06/19] added sorting of request bodies to reduce number of random changes in generated code, fixed content type in RequestBody's comment --- internal/test/components/components.gen.go | 2 +- pkg/codegen/operations.go | 4 ++++ pkg/codegen/templates/request-bodies.tmpl | 3 ++- pkg/codegen/templates/templates.gen.go | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 7fb9f6d96d..8ed3802ede 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -120,7 +120,7 @@ type BodyWithAddPropsJSONBody_Inner struct { // EnsureEverythingIsReferencedJSONRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. type EnsureEverythingIsReferencedJSONRequestBody RequestBody -// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. +// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for text/plain ContentType. type EnsureEverythingIsReferencedTextRequestBody RequestBody // BodyWithAddPropsJSONRequestBody defines body for BodyWithAddProps for application/json ContentType. diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 79ea8c2410..b5ba9d01be 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -17,6 +17,7 @@ import ( "bufio" "bytes" "fmt" + "sort" "strconv" "strings" "text/template" @@ -644,6 +645,9 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody } bodyDefinitions = append(bodyDefinitions, bd) } + sort.Slice(bodyDefinitions, func(i, j int) bool { + return bodyDefinitions[i].ContentType < bodyDefinitions[j].ContentType + }) return bodyDefinitions, typeDefinitions, nil } diff --git a/pkg/codegen/templates/request-bodies.tmpl b/pkg/codegen/templates/request-bodies.tmpl index 473f7030d4..3018717207 100644 --- a/pkg/codegen/templates/request-bodies.tmpl +++ b/pkg/codegen/templates/request-bodies.tmpl @@ -1,8 +1,9 @@ {{range .}}{{$opid := .OperationId}} {{range .Bodies}} {{if .IsSupported -}} +{{$contentType := .ContentType -}} {{with .TypeDef $opid}} -// {{.TypeName}} defines body for {{$opid}} for application/json ContentType. +// {{.TypeName}} defines body for {{$opid}} for {{$contentType}} ContentType. type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} {{end}} {{end}} diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index ed835a5f8a..eb417ff8fc 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -1321,8 +1321,9 @@ type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.Ty "request-bodies.tmpl": `{{range .}}{{$opid := .OperationId}} {{range .Bodies}} {{if .IsSupported -}} +{{$contentType := .ContentType -}} {{with .TypeDef $opid}} -// {{.TypeName}} defines body for {{$opid}} for application/json ContentType. +// {{.TypeName}} defines body for {{$opid}} for {{$contentType}} ContentType. type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} {{end}} {{end}} From 86a4606d38b29470ab12f27d354947d2d3bb880a Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Thu, 27 Jan 2022 09:53:02 +0300 Subject: [PATCH 07/19] Implemented basic formdata binder (not yet integrated into strict server) --- pkg/codegen/operations.go | 18 ++++ pkg/codegen/schema.go | 3 +- pkg/runtime/bindform.go | 179 +++++++++++++++++++++++++++++++++++ pkg/runtime/bindform_test.go | 144 ++++++++++++++++++++++++++++ pkg/types/file.go | 71 ++++++++++++++ 5 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 pkg/runtime/bindform.go create mode 100644 pkg/runtime/bindform_test.go create mode 100644 pkg/types/file.go diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index b5ba9d01be..b52df899e1 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -345,6 +345,9 @@ type RequestBodyDefinition struct { // Whether this is the default body type. For an operation named OpFoo, we // will not add suffixes like OpFooJSONBody for this one. Default bool + + // Contains encoding options for formdata + Encoding map[string]RequestBodyEncoding } // Returns the Go type definition for a request body @@ -388,6 +391,12 @@ func (r RequestBodyDefinition) IsFixedContentType() bool { return !strings.Contains(r.ContentType, "*") } +type RequestBodyEncoding struct { + ContentType string + Style string + Explode *bool +} + type ResponseDefinition struct { StatusCode string Description string @@ -643,6 +652,15 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody ContentType: contentType, Default: defaultBody, } + + if len(content.Encoding) != 0 { + bd.Encoding = make(map[string]RequestBodyEncoding) + for k, v := range content.Encoding { + encoding := RequestBodyEncoding{ContentType: v.ContentType, Style: v.Style, Explode: v.Explode} + bd.Encoding[k] = encoding + } + } + bodyDefinitions = append(bodyDefinitions, bd) } sort.Slice(bodyDefinitions, func(i, j int) bool { diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 246f7e7218..4fbd80b3c0 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -393,8 +393,7 @@ func resolveType(schema *openapi3.Schema, path []string, outSchema *Schema) erro outSchema.GoType = "json.RawMessage" outSchema.SkipOptionalPointer = true case "binary": - outSchema.GoType = "*multipart.FileHeader" - outSchema.SkipOptionalPointer = true + outSchema.GoType = "openapi_types.File" default: // All unrecognized formats are simply a regular string. outSchema.GoType = "string" diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go new file mode 100644 index 0000000000..37cc3d6c16 --- /dev/null +++ b/pkg/runtime/bindform.go @@ -0,0 +1,179 @@ +package runtime + +import ( + "encoding/json" + "errors" + "fmt" + "mime/multipart" + "reflect" + "strconv" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/codegen" + "github.com/deepmap/oapi-codegen/pkg/types" +) + +const tagName = "json" +const jsonContentType = "application/json" + +func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]codegen.RequestBodyEncoding) error { + ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) + if ptrVal.Kind() != reflect.Struct { + return errors.New("form data body should be a struct") + } + tValue := ptrVal.Type() + + for i := 0; i < tValue.NumField(); i++ { + field := tValue.Field(i) + tag := field.Tag.Get(tagName) + if !field.IsExported() || tag == "-" { + continue + } + tag = strings.Split(tag, ",")[0] // extract the name of the tag + if encoding, ok := encodings[tag]; ok { + // custom encoding + values := form[tag] + if len(values) == 0 { + continue + } + value := values[0] + if encoding.ContentType != "" { + if strings.HasPrefix(encoding.ContentType, jsonContentType) { + if err := json.Unmarshal([]byte(value), ptr); err != nil { + return err + } + } + return errors.New("unsupported encoding, only application/json is supported") + } else { + var explode bool + if encoding.Explode != nil { + explode = *encoding.Explode + } + if err := BindStyledParameterWithLocation(encoding.Style, explode, tag, ParamLocationUndefined, value, ptrVal.Field(i).Addr().Interface()); err != nil { + return err + } + } + } else { + // regular form data + if _, err := bindFormImpl(ptrVal.Field(i), form, files, tag); err != nil { + return err + } + } + } + + return nil +} + +func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { + var hasData bool + switch v.Kind() { + case reflect.Interface: + return bindFormImpl(v.Elem(), form, files, name) + case reflect.Ptr: + ptrData := v.Elem() + if !ptrData.IsValid() { + ptrData = reflect.New(v.Type().Elem()) + } + ptrHasData, err := bindFormImpl(ptrData, form, files, name) + if err == nil && ptrHasData && !v.Elem().IsValid() { + v.Set(ptrData) + } + return ptrHasData, err + case reflect.Slice: + if files := append(files[name], files[name+"[]"]...); len(files) != 0 { + if _, ok := v.Interface().([]types.File); ok { + result := make([]types.File, len(files), len(files)) + for i, file := range files { + result[i].InitFromMultipart(file) + } + v.Set(reflect.ValueOf(result)) + hasData = true + } + } + indexedElementsCount := indexedElementsCount(form, files, name) + items := append(form[name], form[name+"[]"]...) + if indexedElementsCount+len(items) != 0 { + result := reflect.MakeSlice(v.Type(), indexedElementsCount+len(items), indexedElementsCount+len(items)) + for i := 0; i < indexedElementsCount; i++ { + if _, err := bindFormImpl(result.Index(i), form, files, fmt.Sprintf("%s[%v]", name, i)); err != nil { + return false, err + } + } + for i, item := range items { + if err := BindStringToObject(item, result.Index(indexedElementsCount+i).Addr().Interface()); err != nil { + return false, err + } + } + v.Set(result) + hasData = true + } + case reflect.Struct: + if files := files[name]; len(files) != 0 { + if file, ok := v.Interface().(types.File); ok { + file.InitFromMultipart(files[0]) + v.Set(reflect.ValueOf(file)) + return true, nil + } + } + for i := 0; i < v.NumField(); i++ { + field := v.Type().Field(i) + tag := field.Tag.Get(tagName) + if field.Name == "AdditionalProperties" && tag == "-" { + additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), form, files, name) + if err != nil { + return false, err + } + hasData = hasData || additionalPropertiesHasData + } + if !field.IsExported() || tag == "-" { + continue + } + tag = strings.Split(tag, ",")[0] // extract the name of the tag + fieldHasData, err := bindFormImpl(v.Field(i), form, files, fmt.Sprintf("%s[%s]", name, tag)) + if err != nil { + return false, err + } + hasData = hasData || fieldHasData + } + return hasData, nil + default: + value := form[name] + if len(value) != 0 { + return true, BindStringToObject(value[0], v.Addr().Interface()) + } + } + return hasData, nil +} + +func indexedElementsCount(form map[string][]string, files map[string][]*multipart.FileHeader, name string) int { + name += "[" + maxIndex := -1 + for k := range form { + if strings.HasPrefix(k, name) { + str := strings.TrimPrefix(k, name) + str = str[:strings.Index(str, "]")] + if idx, err := strconv.Atoi(str); err == nil { + if idx > maxIndex { + maxIndex = idx + } + } + } + } + for k := range files { + if strings.HasPrefix(k, name) { + str := strings.TrimPrefix(k, name) + str = str[:strings.Index(str, "]")] + if idx, err := strconv.Atoi(str); err == nil { + if idx > maxIndex { + maxIndex = idx + } + } + } + } + return maxIndex + 1 +} + +func bindAdditionalProperties(additionalProperties reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { + //TODO: support additional properties + return false, nil +} diff --git a/pkg/runtime/bindform_test.go b/pkg/runtime/bindform_test.go new file mode 100644 index 0000000000..14c6980f75 --- /dev/null +++ b/pkg/runtime/bindform_test.go @@ -0,0 +1,144 @@ +package runtime + +import ( + "bytes" + "mime/multipart" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/deepmap/oapi-codegen/pkg/types" +) + +func TestBindURLForm(t *testing.T) { + type testSubStruct struct { + Int int `json:"int"` + String string `json:"string"` + } + type testStruct struct { + Int int `json:"int"` + Bool bool `json:"bool,omitempty"` + String string `json:"string"` + IntSlice []int `json:"int_slice"` + Struct testSubStruct `json:"struct"` + StructSlice []testSubStruct `json:"struct_slice"` + OptInt *int `json:"opt_int,omitempty"` + OptBool *bool `json:"opt_bool,omitempty"` + OptString *string `json:"opt_string,omitempty"` + OptStruct *testSubStruct `json:"opt_struct,omitempty"` + OptStructSlice *[]testSubStruct `json:"opt_struct_slice,omitempty"` + NotSerializable int `json:"-"` + unexported int + } + + testCases := map[string]testStruct{ + "int=123": {Int: 123}, + "bool=true": {Bool: true}, + "string=example": {String: "example"}, + "int_slice=1&int_slice=2&int_slice=3": {IntSlice: []int{1, 2, 3}}, + "int_slice[]=1&int_slice[]=2&int_slice[]=3": {IntSlice: []int{1, 2, 3}}, + "int_slice[2]=3&int_slice[1]=2&int_slice[0]=1": {IntSlice: []int{1, 2, 3}}, + "struct[int]=789&struct[string]=abc": {Struct: testSubStruct{Int: 789, String: "abc"}}, + "struct_slice[0][int]=3&struct_slice[0][string]=a&struct_slice[1][int]=2&struct_slice[1][string]=b&struct_slice[2][int]=1&struct_slice[2][string]=c": { + StructSlice: []testSubStruct{{Int: 3, String: "a"}, {Int: 2, String: "b"}, {Int: 1, String: "c"}}, + }, + "opt_int=456": {OptInt: func(v int) *int { return &v }(456)}, + "opt_bool=true": {OptBool: func(v bool) *bool { return &v }(true)}, + "opt_string=def": {OptString: func(v string) *string { return &v }("def")}, + "opt_struct[int]=456&opt_struct[string]=def": {OptStruct: &testSubStruct{Int: 456, String: "def"}}, + "opt_struct_slice[0][int]=123&opt_struct_slice[0][string]=abc&opt_struct_slice[1][int]=456&opt_struct_slice[1][string]=def": { + OptStructSlice: &([]testSubStruct{{Int: 123, String: "abc"}, {Int: 456, String: "def"}}), + }, + } + + for k, v := range testCases { + values, err := url.ParseQuery(k) + assert.NoError(t, err) + var result testStruct + err = BindForm(&result, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, v, result) + } +} + +func TestBindMultipartForm(t *testing.T) { + var testStruct struct { + File types.File `json:"file"` + OptFile *types.File `json:"opt_file,omitempty"` + Files []types.File `json:"files"` + OptFiles *[]types.File `json:"opt_files"` + } + + form, err := makeMultipartFilesForm([]fileData{{field: "file", filename: "123.txt", content: []byte("123")}}) + assert.NoError(t, err) + err = BindForm(&testStruct, form.Value, form.File, nil) + assert.NoError(t, err) + assert.Equal(t, "123.txt", testStruct.File.Filename()) + content, err := testStruct.File.Bytes() + assert.NoError(t, err) + assert.Equal(t, []byte("123"), content) + + form, err = makeMultipartFilesForm([]fileData{ + {field: "files", filename: "123.pdf", content: []byte("123")}, + {field: "files", filename: "456.pdf", content: []byte("456")}, + {field: "files", filename: "789.pdf", content: []byte("789")}, + }) + assert.NoError(t, err) + err = BindForm(&testStruct, form.Value, form.File, nil) + assert.NoError(t, err) + assert.Equal(t, 3, len(testStruct.Files)) + assert.Equal(t, "123.pdf", testStruct.Files[0].Filename()) + assert.Equal(t, "456.pdf", testStruct.Files[1].Filename()) + assert.Equal(t, "789.pdf", testStruct.Files[2].Filename()) + + form, err = makeMultipartFilesForm([]fileData{{field: "opt_file", filename: "456.png", content: []byte("456")}}) + assert.NoError(t, err) + err = BindForm(&testStruct, form.Value, form.File, nil) + assert.NoError(t, err) + assert.Equal(t, "456.png", testStruct.OptFile.Filename()) + content, err = testStruct.OptFile.Bytes() + assert.NoError(t, err) + assert.Equal(t, []byte("456"), content) + + form, err = makeMultipartFilesForm([]fileData{ + {field: "opt_files[2]", filename: "123.pdf", content: []byte("123")}, + {field: "opt_files[1]", filename: "456.pdf", content: []byte("456")}, + {field: "opt_files[0]", filename: "789.pdf", content: []byte("789")}, + }) + assert.NoError(t, err) + err = BindForm(&testStruct, form.Value, form.File, nil) + assert.NoError(t, err) + assert.NotNil(t, testStruct.OptFiles) + assert.Equal(t, 3, len(*testStruct.OptFiles)) + assert.Equal(t, "789.pdf", (*testStruct.OptFiles)[0].Filename()) + assert.Equal(t, "456.pdf", (*testStruct.OptFiles)[1].Filename()) + assert.Equal(t, "123.pdf", (*testStruct.OptFiles)[2].Filename()) +} + +type fileData struct { + field string + filename string + content []byte +} + +func makeMultipartFilesForm(files []fileData) (*multipart.Form, error) { + var buffer bytes.Buffer + mw := multipart.NewWriter(&buffer) + for _, file := range files { + w, err := mw.CreateFormFile(file.field, file.filename) + if err != nil { + return nil, err + } + _, err = w.Write(file.content) + if err != nil { + return nil, err + } + } + err := mw.Close() + if err != nil { + return nil, err + } + mr := multipart.NewReader(&buffer, mw.Boundary()) + return mr.ReadForm(1024) +} diff --git a/pkg/types/file.go b/pkg/types/file.go new file mode 100644 index 0000000000..f0ad3808c7 --- /dev/null +++ b/pkg/types/file.go @@ -0,0 +1,71 @@ +package types + +import ( + "bytes" + "encoding/json" + "io" + "mime/multipart" +) + +type File struct { + multipart *multipart.FileHeader + data []byte + filename string +} + +func (file *File) InitFromMultipart(header *multipart.FileHeader) { + file.multipart = header + file.data = nil + file.filename = "" +} + +func (file *File) InitFromBytes(data []byte, filename string) { + file.data = data + file.filename = filename + file.multipart = nil +} + +func (file *File) MarshalJSON() ([]byte, error) { + b, err := file.Bytes() + if err != nil { + return nil, err + } + return json.Marshal(b) +} + +func (file *File) UnmarshalJSON(data []byte) error { + return json.Unmarshal(data, &file.data) +} + +func (file File) Bytes() ([]byte, error) { + if file.multipart != nil { + f, err := file.multipart.Open() + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + return io.ReadAll(f) + } + return file.data, nil +} + +func (file File) Reader() (io.ReadCloser, error) { + if file.multipart != nil { + return file.multipart.Open() + } + return io.NopCloser(bytes.NewReader(file.data)), nil +} + +func (file File) Filename() string { + if file.multipart != nil { + return file.multipart.Filename + } + return file.filename +} + +func (file File) FileSize() int64 { + if file.multipart != nil { + return file.multipart.Size + } + return int64(len(file.data)) +} From d3434b9ad149df2bb82616f58c958b0cd2afc51a Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Wed, 20 Apr 2022 13:23:52 +0300 Subject: [PATCH 08/19] Added tests for strict generation, support for form marshalling, client improvements --- examples/strict-server/chi/server.gen.go | 915 ++++++++++++++++ examples/strict-server/chi/server.go | 3 + examples/strict-server/client/client.gen.go | 977 ++++++++++++++++++ examples/strict-server/client/client.go | 3 + examples/strict-server/echo/server.gen.go | 732 +++++++++++++ examples/strict-server/echo/server.go | 3 + examples/strict-server/gin/server.gen.go | 727 +++++++++++++ examples/strict-server/gin/server.go | 3 + examples/strict-server/strict-schema.yaml | 165 +++ internal/test/components/components.gen.go | 31 + pkg/codegen/codegen.go | 2 +- pkg/codegen/operations.go | 29 +- pkg/codegen/templates/client.tmpl | 20 +- pkg/codegen/templates/strict/strict-chi.tmpl | 22 +- pkg/codegen/templates/strict/strict-echo.tmpl | 20 +- pkg/codegen/templates/strict/strict-gin.tmpl | 23 +- .../templates/strict/strict-interface.tmpl | 4 +- pkg/runtime/bindform.go | 77 +- pkg/runtime/bindform_test.go | 48 + 19 files changed, 3767 insertions(+), 37 deletions(-) create mode 100644 examples/strict-server/chi/server.gen.go create mode 100644 examples/strict-server/chi/server.go create mode 100644 examples/strict-server/client/client.gen.go create mode 100644 examples/strict-server/client/client.go create mode 100644 examples/strict-server/echo/server.gen.go create mode 100644 examples/strict-server/echo/server.go create mode 100644 examples/strict-server/gin/server.gen.go create mode 100644 examples/strict-server/gin/server.go create mode 100644 examples/strict-server/strict-schema.yaml diff --git a/examples/strict-server/chi/server.gen.go b/examples/strict-server/chi/server.gen.go new file mode 100644 index 0000000000..8540398d89 --- /dev/null +++ b/examples/strict-server/chi/server.gen.go @@ -0,0 +1,915 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" +) + +// Badrequest defines model for badrequest. +type Badrequest string + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// JSONExampleJSONBody defines parameters for JSONExample. +type JSONExampleJSONBody Example + +// MultipartExampleMultipartBody defines parameters for MultipartExample. +type MultipartExampleMultipartBody Example + +// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesJSONBody Example + +// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesFormdataBody Example + +// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesMultipartBody Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody string + +// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. +type URLEncodedExampleFormdataBody Example + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody JSONExampleJSONBody + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(w http.ResponseWriter, r *http.Request) + + // (POST /multipart) + MultipartExample(w http.ResponseWriter, r *http.Request) + + // (POST /multiple) + MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) + + // (POST /text) + TextExample(w http.ResponseWriter, r *http.Request) + + // (POST /unknown) + UnknownExample(w http.ResponseWriter, r *http.Request) + + // (POST /urlencoded) + URLEncodedExample(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.JSONExample(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipartExample(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MultipleRequestAndResponseTypes(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.TextExample(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnknownExample(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.URLEncodedExample(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/json", wrapper.JSONExample) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/multipart", wrapper.MultipartExample) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/text", wrapper.TextExample) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/unknown", wrapper.UnknownExample) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + }) + + return r +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400TextResponse Badrequest + +func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipartExample400TextResponse Badrequest + +func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExample200TextResponse string + +func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type TextExample400TextResponse Badrequest + +func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type UnknownExample400TextResponse Badrequest + +func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +type URLEncodedExample200FormdataResponse Example + +func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type URLEncodedExample400TextResponse Badrequest + +func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) interface{} + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} +} + +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case JSONExample200JSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case JSONExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case JSONExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request) { + var request MultipartExampleRequestObject + + if reader, err := r.MultipartReader(); err != nil { + http.Error(w, "can't decode multipart body: "+err.Error(), http.StatusBadRequest) + return + } else { + request.Body = reader + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.MultipartExample(ctx, request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case MultipartExample200MultipartformDataResponse: + w.Header().Set("Content-Type", "multipart/form-data") + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + w.WriteHeader(200) + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) + case MultipartExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case MultipartExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest) + return + } + request.JSONBody = &body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { + if err := r.ParseForm(); err != nil { + http.Error(w, "can't decode formdata: "+err.Error(), http.StatusBadRequest) + return + } + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + http.Error(w, "can't bind formdata: "+err.Error(), http.StatusBadRequest) + return + } + request.FormdataBody = &body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "image/png") { + request.Body = r.Body + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { + if reader, err := r.MultipartReader(); err != nil { + http.Error(w, "can't decode multipart body: "+err.Error(), http.StatusBadRequest) + return + } else { + request.MultipartBody = reader + } + } + if strings.HasPrefix(r.Header.Get("Content-Type"), "text/plain") { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body: "+err.Error(), http.StatusBadRequest) + return + } + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.MultipleRequestAndResponseTypes(ctx, request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case MultipleRequestAndResponseTypes200TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + writeRaw(w, ([]byte)(v)) + case MultipleRequestAndResponseTypes200JSONResponse: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case MultipleRequestAndResponseTypes200FormdataResponse: + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + if form, err := runtime.MarshalForm(v, nil); err != nil { + fmt.Fprintln(w, err) + } else { + writeRaw(w, []byte(form.Encode())) + } + case MultipleRequestAndResponseTypes200ImagepngResponse: + w.Header().Set("Content-Type", "image/png") + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + w.WriteHeader(200) + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) + case MultipleRequestAndResponseTypes200MultipartformDataResponse: + w.Header().Set("Content-Type", "multipart/form-data") + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + w.WriteHeader(200) + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { + var request TextExampleRequestObject + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body: "+err.Error(), http.StatusBadRequest) + return + } + body := TextExampleTextRequestBody(data) + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case TextExample200TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + writeRaw(w, ([]byte)(v)) + case TextExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case TextExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) { + var request UnknownExampleRequestObject + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.UnknownExample(ctx, request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case UnknownExample200Videomp4Response: + w.Header().Set("Content-Type", "video/mp4") + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + w.WriteHeader(200) + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) + case UnknownExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case UnknownExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + var request URLEncodedExampleRequestObject + + if err := r.ParseForm(); err != nil { + http.Error(w, "can't decode formdata: "+err.Error(), http.StatusBadRequest) + return + } + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + http.Error(w, "can't bind formdata: "+err.Error(), http.StatusBadRequest) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.URLEncodedExample(ctx, request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case URLEncodedExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case URLEncodedExample200FormdataResponse: + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.WriteHeader(200) + if form, err := runtime.MarshalForm(v, nil); err != nil { + fmt.Fprintln(w, err) + } else { + writeRaw(w, []byte(form.Encode())) + } + case URLEncodedExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +func writeJSON(w http.ResponseWriter, v interface{}) { + if err := json.NewEncoder(w).Encode(v); err != nil { + fmt.Fprintln(w, err) + } +} + +func writeRaw(w http.ResponseWriter, b []byte) { + if _, err := w.Write(b); err != nil { + fmt.Fprintln(w, err) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", + "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", + "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", + "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", + "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", + "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", + "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", + "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", + "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", + "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", + "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/strict-server/chi/server.go b/examples/strict-server/chi/server.go new file mode 100644 index 0000000000..8ce159176f --- /dev/null +++ b/examples/strict-server/chi/server.go @@ -0,0 +1,3 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,spec,strict-server -o server.gen.go ../strict-schema.yaml + +package api diff --git a/examples/strict-server/client/client.gen.go b/examples/strict-server/client/client.gen.go new file mode 100644 index 0000000000..b989d02dca --- /dev/null +++ b/examples/strict-server/client/client.gen.go @@ -0,0 +1,977 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" +) + +// Badrequest defines model for badrequest. +type Badrequest string + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// JSONExampleJSONBody defines parameters for JSONExample. +type JSONExampleJSONBody Example + +// MultipartExampleMultipartBody defines parameters for MultipartExample. +type MultipartExampleMultipartBody Example + +// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesMultipartBody Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody string + +// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesJSONBody Example + +// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesFormdataBody Example + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody string + +// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. +type URLEncodedExampleFormdataBody Example + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody JSONExampleJSONBody + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // JSONExample request with any body + JSONExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + JSONExample(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // MultipartExample request with any body + MultipartExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // MultipleRequestAndResponseTypes request with any body + MultipleRequestAndResponseTypesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + MultipleRequestAndResponseTypes(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + MultipleRequestAndResponseTypesWithFormdataBody(ctx context.Context, body MultipleRequestAndResponseTypesFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + MultipleRequestAndResponseTypesWithTextBody(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // TextExample request with any body + TextExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TextExampleWithTextBody(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UnknownExample request with any body + UnknownExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // URLEncodedExample request with any body + URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + URLEncodedExampleWithFormdataBody(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) JSONExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewJSONExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) JSONExample(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewJSONExampleRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MultipartExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipartExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MultipleRequestAndResponseTypesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipleRequestAndResponseTypesRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MultipleRequestAndResponseTypes(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipleRequestAndResponseTypesRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MultipleRequestAndResponseTypesWithFormdataBody(ctx context.Context, body MultipleRequestAndResponseTypesFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipleRequestAndResponseTypesRequestWithFormdataBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MultipleRequestAndResponseTypesWithTextBody(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMultipleRequestAndResponseTypesRequestWithTextBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TextExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTextExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TextExampleWithTextBody(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTextExampleRequestWithTextBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UnknownExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnknownExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewURLEncodedExampleRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) URLEncodedExampleWithFormdataBody(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewURLEncodedExampleRequestWithFormdataBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewJSONExampleRequest calls the generic JSONExample builder with application/json body +func NewJSONExampleRequest(server string, body JSONExampleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewJSONExampleRequestWithBody(server, "application/json", bodyReader) +} + +// NewJSONExampleRequestWithBody generates requests for JSONExample with any type of body +func NewJSONExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/json") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewMultipartExampleRequestWithBody generates requests for MultipartExample with any type of body +func NewMultipartExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/multipart") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewMultipleRequestAndResponseTypesRequest calls the generic MultipleRequestAndResponseTypes builder with application/json body +func NewMultipleRequestAndResponseTypesRequest(server string, body MultipleRequestAndResponseTypesJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewMultipleRequestAndResponseTypesRequestWithBody(server, "application/json", bodyReader) +} + +// NewMultipleRequestAndResponseTypesRequestWithFormdataBody calls the generic MultipleRequestAndResponseTypes builder with application/x-www-form-urlencoded body +func NewMultipleRequestAndResponseTypesRequestWithFormdataBody(server string, body MultipleRequestAndResponseTypesFormdataRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyStr, err := runtime.MarshalForm(body, nil) + if err != nil { + return nil, err + } + bodyReader = strings.NewReader(bodyStr.Encode()) + return NewMultipleRequestAndResponseTypesRequestWithBody(server, "application/x-www-form-urlencoded", bodyReader) +} + +// NewMultipleRequestAndResponseTypesRequestWithTextBody calls the generic MultipleRequestAndResponseTypes builder with text/plain body +func NewMultipleRequestAndResponseTypesRequestWithTextBody(server string, body MultipleRequestAndResponseTypesTextRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyReader = strings.NewReader(string(body)) + return NewMultipleRequestAndResponseTypesRequestWithBody(server, "text/plain", bodyReader) +} + +// NewMultipleRequestAndResponseTypesRequestWithBody generates requests for MultipleRequestAndResponseTypes with any type of body +func NewMultipleRequestAndResponseTypesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/multiple") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewTextExampleRequestWithTextBody calls the generic TextExample builder with text/plain body +func NewTextExampleRequestWithTextBody(server string, body TextExampleTextRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyReader = strings.NewReader(string(body)) + return NewTextExampleRequestWithBody(server, "text/plain", bodyReader) +} + +// NewTextExampleRequestWithBody generates requests for TextExample with any type of body +func NewTextExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/text") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewUnknownExampleRequestWithBody generates requests for UnknownExample with any type of body +func NewUnknownExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/unknown") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewURLEncodedExampleRequestWithFormdataBody calls the generic URLEncodedExample builder with application/x-www-form-urlencoded body +func NewURLEncodedExampleRequestWithFormdataBody(server string, body URLEncodedExampleFormdataRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyStr, err := runtime.MarshalForm(body, nil) + if err != nil { + return nil, err + } + bodyReader = strings.NewReader(bodyStr.Encode()) + return NewURLEncodedExampleRequestWithBody(server, "application/x-www-form-urlencoded", bodyReader) +} + +// NewURLEncodedExampleRequestWithBody generates requests for URLEncodedExample with any type of body +func NewURLEncodedExampleRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/urlencoded") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // JSONExample request with any body + JSONExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) + + JSONExampleWithResponse(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) + + // MultipartExample request with any body + MultipartExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipartExampleResponse, error) + + // MultipleRequestAndResponseTypes request with any body + MultipleRequestAndResponseTypesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) + + MultipleRequestAndResponseTypesWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) + + MultipleRequestAndResponseTypesWithFormdataBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesFormdataRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) + + MultipleRequestAndResponseTypesWithTextBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) + + // TextExample request with any body + TextExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) + + TextExampleWithTextBodyWithResponse(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) + + // UnknownExample request with any body + UnknownExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnknownExampleResponse, error) + + // URLEncodedExample request with any body + URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) + + URLEncodedExampleWithFormdataBodyWithResponse(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) +} + +type JSONExampleResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Example +} + +// Status returns HTTPResponse.Status +func (r JSONExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r JSONExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type MultipartExampleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r MultipartExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r MultipartExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type MultipleRequestAndResponseTypesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Example +} + +// Status returns HTTPResponse.Status +func (r MultipleRequestAndResponseTypesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r MultipleRequestAndResponseTypesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type TextExampleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r TextExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TextExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UnknownExampleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UnknownExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UnknownExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type URLEncodedExampleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r URLEncodedExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r URLEncodedExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// JSONExampleWithBodyWithResponse request with arbitrary body returning *JSONExampleResponse +func (c *ClientWithResponses) JSONExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) { + rsp, err := c.JSONExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseJSONExampleResponse(rsp) +} + +func (c *ClientWithResponses) JSONExampleWithResponse(ctx context.Context, body JSONExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) { + rsp, err := c.JSONExample(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseJSONExampleResponse(rsp) +} + +// MultipartExampleWithBodyWithResponse request with arbitrary body returning *MultipartExampleResponse +func (c *ClientWithResponses) MultipartExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipartExampleResponse, error) { + rsp, err := c.MultipartExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipartExampleResponse(rsp) +} + +// MultipleRequestAndResponseTypesWithBodyWithResponse request with arbitrary body returning *MultipleRequestAndResponseTypesResponse +func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) { + rsp, err := c.MultipleRequestAndResponseTypesWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipleRequestAndResponseTypesResponse(rsp) +} + +func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesJSONRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) { + rsp, err := c.MultipleRequestAndResponseTypes(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipleRequestAndResponseTypesResponse(rsp) +} + +func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithFormdataBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesFormdataRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) { + rsp, err := c.MultipleRequestAndResponseTypesWithFormdataBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipleRequestAndResponseTypesResponse(rsp) +} + +func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithTextBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) { + rsp, err := c.MultipleRequestAndResponseTypesWithTextBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMultipleRequestAndResponseTypesResponse(rsp) +} + +// TextExampleWithBodyWithResponse request with arbitrary body returning *TextExampleResponse +func (c *ClientWithResponses) TextExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) { + rsp, err := c.TextExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTextExampleResponse(rsp) +} + +func (c *ClientWithResponses) TextExampleWithTextBodyWithResponse(ctx context.Context, body TextExampleTextRequestBody, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) { + rsp, err := c.TextExampleWithTextBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTextExampleResponse(rsp) +} + +// UnknownExampleWithBodyWithResponse request with arbitrary body returning *UnknownExampleResponse +func (c *ClientWithResponses) UnknownExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnknownExampleResponse, error) { + rsp, err := c.UnknownExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnknownExampleResponse(rsp) +} + +// URLEncodedExampleWithBodyWithResponse request with arbitrary body returning *URLEncodedExampleResponse +func (c *ClientWithResponses) URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) { + rsp, err := c.URLEncodedExampleWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseURLEncodedExampleResponse(rsp) +} + +func (c *ClientWithResponses) URLEncodedExampleWithFormdataBodyWithResponse(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) { + rsp, err := c.URLEncodedExampleWithFormdataBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseURLEncodedExampleResponse(rsp) +} + +// ParseJSONExampleResponse parses an HTTP response from a JSONExampleWithResponse call +func ParseJSONExampleResponse(rsp *http.Response) (*JSONExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &JSONExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseMultipartExampleResponse parses an HTTP response from a MultipartExampleWithResponse call +func ParseMultipartExampleResponse(rsp *http.Response) (*MultipartExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MultipartExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseMultipleRequestAndResponseTypesResponse parses an HTTP response from a MultipleRequestAndResponseTypesWithResponse call +func ParseMultipleRequestAndResponseTypesResponse(rsp *http.Response) (*MultipleRequestAndResponseTypesResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MultipleRequestAndResponseTypesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case rsp.StatusCode == 200: + // Content-type (text/plain) unsupported + + } + + return response, nil +} + +// ParseTextExampleResponse parses an HTTP response from a TextExampleWithResponse call +func ParseTextExampleResponse(rsp *http.Response) (*TextExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TextExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseUnknownExampleResponse parses an HTTP response from a UnknownExampleWithResponse call +func ParseUnknownExampleResponse(rsp *http.Response) (*UnknownExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UnknownExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseURLEncodedExampleResponse parses an HTTP response from a URLEncodedExampleWithResponse call +func ParseURLEncodedExampleResponse(rsp *http.Response) (*URLEncodedExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &URLEncodedExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/examples/strict-server/client/client.go b/examples/strict-server/client/client.go new file mode 100644 index 0000000000..3fa0c1e76b --- /dev/null +++ b/examples/strict-server/client/client.go @@ -0,0 +1,3 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,client -o client.gen.go ../strict-schema.yaml + +package api diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go new file mode 100644 index 0000000000..c37163042c --- /dev/null +++ b/examples/strict-server/echo/server.gen.go @@ -0,0 +1,732 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" +) + +// Badrequest defines model for badrequest. +type Badrequest string + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// JSONExampleJSONBody defines parameters for JSONExample. +type JSONExampleJSONBody Example + +// MultipartExampleMultipartBody defines parameters for MultipartExample. +type MultipartExampleMultipartBody Example + +// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesFormdataBody Example + +// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesMultipartBody Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody string + +// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesJSONBody Example + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody string + +// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. +type URLEncodedExampleFormdataBody Example + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody JSONExampleJSONBody + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(ctx echo.Context) error + + // (POST /multipart) + MultipartExample(ctx echo.Context) error + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx echo.Context) error + + // (POST /text) + TextExample(ctx echo.Context) error + + // (POST /unknown) + UnknownExample(ctx echo.Context) error + + // (POST /urlencoded) + URLEncodedExample(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// JSONExample converts echo context to params. +func (w *ServerInterfaceWrapper) JSONExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.JSONExample(ctx) + return err +} + +// MultipartExample converts echo context to params. +func (w *ServerInterfaceWrapper) MultipartExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.MultipartExample(ctx) + return err +} + +// MultipleRequestAndResponseTypes converts echo context to params. +func (w *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.MultipleRequestAndResponseTypes(ctx) + return err +} + +// TextExample converts echo context to params. +func (w *ServerInterfaceWrapper) TextExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.TextExample(ctx) + return err +} + +// UnknownExample converts echo context to params. +func (w *ServerInterfaceWrapper) UnknownExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UnknownExample(ctx) + return err +} + +// URLEncodedExample converts echo context to params. +func (w *ServerInterfaceWrapper) URLEncodedExample(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.URLEncodedExample(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.POST(baseURL+"/json", wrapper.JSONExample) + router.POST(baseURL+"/multipart", wrapper.MultipartExample) + router.POST(baseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.POST(baseURL+"/text", wrapper.TextExample) + router.POST(baseURL+"/unknown", wrapper.UnknownExample) + router.POST(baseURL+"/urlencoded", wrapper.URLEncodedExample) + +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400TextResponse Badrequest + +func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipartExample400TextResponse Badrequest + +func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +type TextExample200TextResponse string + +func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type TextExample400TextResponse Badrequest + +func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type UnknownExample400TextResponse Badrequest + +func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExample400TextResponse Badrequest + +func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +type URLEncodedExample200FormdataResponse Example + +func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) interface{} + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} +} + +type StrictHandlerFunc func(ctx echo.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(ctx echo.Context) error { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.JSONExample(ctx.Request().Context(), request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case JSONExample200JSONResponse: + return ctx.JSON(200, v) + case JSONExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case JSONExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(ctx echo.Context) error { + var request MultipartExampleRequestObject + + if reader, err := ctx.Request().MultipartReader(); err != nil { + return err + } else { + request.Body = reader + } + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.MultipartExample(ctx.Request().Context(), request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case MultipartExample200MultipartformDataResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "multipart/form-data", v.Body) + case MultipartExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case MultipartExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.JSONBody = &body + } + if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "application/x-www-form-urlencoded") { + if form, err := ctx.FormParams(); err == nil { + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, form, nil, nil); err != nil { + return err + } + request.FormdataBody = &body + } else { + return err + } + } + if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "image/png") { + request.Body = ctx.Request().Body + } + if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "multipart/form-data") { + if reader, err := ctx.Request().MultipartReader(); err != nil { + return err + } else { + request.MultipartBody = reader + } + } + if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "text/plain") { + data, err := ioutil.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.MultipleRequestAndResponseTypes(ctx.Request().Context(), request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case MultipleRequestAndResponseTypes200ImagepngResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "image/png", v.Body) + case MultipleRequestAndResponseTypes200MultipartformDataResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "multipart/form-data", v.Body) + case MultipleRequestAndResponseTypes200TextResponse: + return ctx.Blob(200, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes200JSONResponse: + return ctx.JSON(200, v) + case MultipleRequestAndResponseTypes200FormdataResponse: + if form, err := runtime.MarshalForm(v, nil); err != nil { + return err + } else { + return ctx.Blob(200, "application/x-www-form-urlencoded", []byte(form.Encode())) + } + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(ctx echo.Context) error { + var request TextExampleRequestObject + + data, err := ioutil.ReadAll(ctx.Request().Body) + if err != nil { + return err + } + body := TextExampleTextRequestBody(data) + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.TextExample(ctx.Request().Context(), request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case TextExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case TextExample200TextResponse: + return ctx.Blob(200, "text/plain", []byte(v)) + case TextExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(ctx echo.Context) error { + var request UnknownExampleRequestObject + + request.Body = ctx.Request().Body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.UnknownExample(ctx.Request().Context(), request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case UnknownExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case UnknownExample200Videomp4Response: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "video/mp4", v.Body) + case UnknownExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { + var request URLEncodedExampleRequestObject + + if form, err := ctx.FormParams(); err == nil { + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, form, nil, nil); err != nil { + return err + } + request.Body = &body + } else { + return err + } + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.URLEncodedExample(ctx.Request().Context(), request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case URLEncodedExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case URLEncodedExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case URLEncodedExample200FormdataResponse: + if form, err := runtime.MarshalForm(v, nil); err != nil { + return err + } else { + return ctx.Blob(200, "application/x-www-form-urlencoded", []byte(form.Encode())) + } + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", + "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", + "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", + "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", + "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", + "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", + "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", + "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", + "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", + "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", + "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/strict-server/echo/server.go b/examples/strict-server/echo/server.go new file mode 100644 index 0000000000..d824806492 --- /dev/null +++ b/examples/strict-server/echo/server.go @@ -0,0 +1,3 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,server,spec,strict-server -o server.gen.go ../strict-schema.yaml + +package api diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go new file mode 100644 index 0000000000..b461f33d70 --- /dev/null +++ b/examples/strict-server/gin/server.gen.go @@ -0,0 +1,727 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" +) + +// Badrequest defines model for badrequest. +type Badrequest string + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// JSONExampleJSONBody defines parameters for JSONExample. +type JSONExampleJSONBody Example + +// MultipartExampleMultipartBody defines parameters for MultipartExample. +type MultipartExampleMultipartBody Example + +// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesJSONBody Example + +// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesFormdataBody Example + +// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesMultipartBody Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody string + +// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. +type URLEncodedExampleFormdataBody Example + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody JSONExampleJSONBody + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(c *gin.Context) + + // (POST /multipart) + MultipartExample(c *gin.Context) + + // (POST /multiple) + MultipleRequestAndResponseTypes(c *gin.Context) + + // (POST /text) + TextExample(c *gin.Context) + + // (POST /unknown) + UnknownExample(c *gin.Context) + + // (POST /urlencoded) + URLEncodedExample(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc +} + +type MiddlewareFunc func(c *gin.Context) + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.JSONExample(c) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.MultipartExample(c) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.MultipleRequestAndResponseTypes(c) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.TextExample(c) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.UnknownExample(c) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.URLEncodedExample(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine { + return RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + } + + router.POST(options.BaseURL+"/json", wrapper.JSONExample) + + router.POST(options.BaseURL+"/multipart", wrapper.MultipartExample) + + router.POST(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + + router.POST(options.BaseURL+"/text", wrapper.TextExample) + + router.POST(options.BaseURL+"/unknown", wrapper.UnknownExample) + + router.POST(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + + return router +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400TextResponse Badrequest + +func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipartExample400TextResponse Badrequest + +func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExample200TextResponse string + +func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + +type TextExample400TextResponse Badrequest + +func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExample400TextResponse Badrequest + +func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExample200FormdataResponse Example + +func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type URLEncodedExample400TextResponse Badrequest + +func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Badrequest)(t)) +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) interface{} + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} +} + +type StrictHandlerFunc func(ctx *gin.Context, args interface{}) interface{} + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// JSONExample operation middleware +func (sh *strictHandler) JSONExample(ctx *gin.Context) { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.JSONExample(ctx, request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case JSONExample200JSONResponse: + ctx.JSON(200, v) + case JSONExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case JSONExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(ctx *gin.Context) { + var request MultipartExampleRequestObject + + if reader, err := ctx.Request.MultipartReader(); err == nil { + request.Body = reader + } else { + ctx.Error(err) + return + } + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.MultipartExample(ctx, request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case MultipartExample200MultipartformDataResponse: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) + case MultipartExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case MultipartExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.JSONBody = &body + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/x-www-form-urlencoded") { + if err := ctx.Request.ParseForm(); err != nil { + ctx.Error(err) + return + } + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := runtime.BindForm(&body, ctx.Request.Form, nil, nil); err != nil { + ctx.Error(err) + return + } + request.FormdataBody = &body + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "image/png") { + request.Body = ctx.Request.Body + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "multipart/form-data") { + if reader, err := ctx.Request.MultipartReader(); err == nil { + request.MultipartBody = reader + } else { + ctx.Error(err) + return + } + } + if strings.HasPrefix(ctx.GetHeader("Content-Type"), "text/plain") { + data, err := ioutil.ReadAll(ctx.Request.Body) + if err != nil { + ctx.Error(err) + return + } + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.MultipleRequestAndResponseTypes(ctx, request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case MultipleRequestAndResponseTypes200MultipartformDataResponse: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) + case MultipleRequestAndResponseTypes200TextResponse: + ctx.Data(200, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes200JSONResponse: + ctx.JSON(200, v) + case MultipleRequestAndResponseTypes200FormdataResponse: + if form, err := runtime.MarshalForm(v, nil); err != nil { + ctx.Error(err) + } else { + ctx.Data(200, "application/x-www-form-urlencoded", []byte(form.Encode())) + } + case MultipleRequestAndResponseTypes200ImagepngResponse: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "image/png", v.Body, nil) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(ctx *gin.Context) { + var request TextExampleRequestObject + + data, err := ioutil.ReadAll(ctx.Request.Body) + if err != nil { + ctx.Error(err) + return + } + body := TextExampleTextRequestBody(data) + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.TextExample(ctx, request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case TextExample200TextResponse: + ctx.Data(200, "text/plain", []byte(v)) + case TextExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case TextExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(ctx *gin.Context) { + var request UnknownExampleRequestObject + + request.Body = ctx.Request.Body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.UnknownExample(ctx, request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case UnknownExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case UnknownExampledefaultResponse: + ctx.Status(v.StatusCode) + case UnknownExample200Videomp4Response: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "video/mp4", v.Body, nil) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(ctx *gin.Context) { + var request URLEncodedExampleRequestObject + + if err := ctx.Request.ParseForm(); err != nil { + ctx.Error(err) + return + } + var body URLEncodedExampleFormdataRequestBody + if err := runtime.BindForm(&body, ctx.Request.Form, nil, nil); err != nil { + ctx.Error(err) + return + } + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.URLEncodedExample(ctx, request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case URLEncodedExample200FormdataResponse: + if form, err := runtime.MarshalForm(v, nil); err != nil { + ctx.Error(err) + } else { + ctx.Data(200, "application/x-www-form-urlencoded", []byte(form.Encode())) + } + case URLEncodedExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case URLEncodedExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", + "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", + "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", + "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", + "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", + "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", + "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", + "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", + "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", + "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", + "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/examples/strict-server/gin/server.go b/examples/strict-server/gin/server.go new file mode 100644 index 0000000000..1e0f5085a5 --- /dev/null +++ b/examples/strict-server/gin/server.go @@ -0,0 +1,3 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,gin,spec,strict-server -o server.gen.go ../strict-schema.yaml + +package api diff --git a/examples/strict-server/strict-schema.yaml b/examples/strict-server/strict-schema.yaml new file mode 100644 index 0000000000..5822c3bb37 --- /dev/null +++ b/examples/strict-server/strict-schema.yaml @@ -0,0 +1,165 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Strict server examples + description: Contains different content types supported by strict server +servers: + - url: http://strict.swagger.io/api +paths: + /json: + post: + operationId: JSONExample + description: JSON is automatically marshalled into structs. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /urlencoded: + post: + operationId: URLEncodedExample + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /multipart: + post: + operationId: MultipartExample + requestBody: + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /text: + post: + operationId: TextExample + requestBody: + content: + text/plain: + schema: + type: string + responses: + 200: + description: OK + content: + text/plain: + schema: + type: string + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /unknown: + post: + operationId: UnknownExample + requestBody: + content: + image/png: + schema: + type: string + format: byte + responses: + 200: + description: OK + content: + video/mp4: + schema: + type: string + format: byte + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error + /multiple: + post: + operationId: Multiple request and response types + description: Shows how to deal with multiple content types in a single request + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/example" + multipart/form-data: + schema: + $ref: "#/components/schemas/example" + text/plain: + schema: + type: string + image/png: + schema: + type: string + format: byte + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/example" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/example" + multipart/form-data: + schema: + $ref: "#/components/schemas/example" + text/plain: + schema: + type: string + image/png: + schema: + type: string + format: byte +components: + responses: + badrequest: + description: BadRequest + content: + text/plain: + schema: + $ref: "#/components/schemas/badrequest" + + schemas: + example: + type: object + properties: + value: + type: string + badrequest: + type: string diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 8ed3802ede..c8f35873d1 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -800,6 +800,8 @@ type ClientInterface interface { EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ParamsWithAddProps request ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -833,6 +835,18 @@ func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, body EnsureEv return c.Client.Do(req) } +func (c *Client) EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnsureEverythingIsReferencedRequestWithTextBody(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewParamsWithAddPropsRequest(c.Server, params) if err != nil { @@ -880,6 +894,13 @@ func NewEnsureEverythingIsReferencedRequest(server string, body EnsureEverything return NewEnsureEverythingIsReferencedRequestWithBody(server, "application/json", bodyReader) } +// NewEnsureEverythingIsReferencedRequestWithTextBody calls the generic EnsureEverythingIsReferenced builder with text/plain body +func NewEnsureEverythingIsReferencedRequestWithTextBody(server string, body EnsureEverythingIsReferencedTextRequestBody) (*http.Request, error) { + var bodyReader io.Reader + bodyReader = strings.NewReader(string(body)) + return NewEnsureEverythingIsReferencedRequestWithBody(server, "text/plain", bodyReader) +} + // NewEnsureEverythingIsReferencedRequestWithBody generates requests for EnsureEverythingIsReferenced with any type of body func NewEnsureEverythingIsReferencedRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -1052,6 +1073,8 @@ type ClientWithResponsesInterface interface { EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) + EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) + // ParamsWithAddProps request ParamsWithAddPropsWithResponse(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*ParamsWithAddPropsResponse, error) @@ -1161,6 +1184,14 @@ func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx conte return ParseEnsureEverythingIsReferencedResponse(rsp) } +func (c *ClientWithResponses) EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { + rsp, err := c.EnsureEverythingIsReferencedWithTextBody(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseEnsureEverythingIsReferencedResponse(rsp) +} + // ParamsWithAddPropsWithResponse request returning *ParamsWithAddPropsResponse func (c *ClientWithResponses) ParamsWithAddPropsWithResponse(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*ParamsWithAddPropsResponse, error) { rsp, err := c.ParamsWithAddProps(ctx, params, reqEditors...) diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 5734286b30..9ded2cc055 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -38,7 +38,7 @@ type Options struct { GenerateChiServer bool // GenerateChiServer specifies whether to generate chi server boilerplate GenerateEchoServer bool // GenerateEchoServer specifies whether to generate echo server boilerplate GenerateGinServer bool // GenerateGinServer specifies whether to generate echo server boilerplate - GenerateStrict bool //GenerateStrict specifies whether to generate strict server wrapper + GenerateStrict bool // GenerateStrict specifies whether to generate strict server wrapper GenerateClient bool // GenerateClient specifies whether to generate client boilerplate GenerateTypes bool // GenerateTypes specifies whether to generate type definitions EmbedSpec bool // Whether to embed the swagger spec in the generated code diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index b1fd0db2d1..6ae008f58c 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -378,7 +378,7 @@ func (r RequestBodyDefinition) Suffix() string { // Returns true if we support this content type for client. Otherwise only generic method will ge generated func (r RequestBodyDefinition) IsSupportedByClient() bool { - return r.NameTag == "JSON" + return r.NameTag == "JSON" || r.NameTag == "Formdata" || r.NameTag == "Text" } // Returns true if we support this content type for server. Otherwise io.Reader will be generated @@ -438,6 +438,13 @@ func (r ResponseContentDefinition) HasFixedContentType() bool { return !strings.Contains(r.ContentType, "*") } +func (r ResponseContentDefinition) NameTagOrContentType() string { + if r.NameTag != "" { + return r.NameTag + } + return SchemaNameToTypeName(r.ContentType) +} + type ResponseHeaderDefinition struct { Name string GoName string @@ -597,15 +604,15 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody var tag string var defaultBody bool - switch contentType { - case "application/json": + switch { + case contentType == "application/json": tag = "JSON" defaultBody = true - case "multipart/form-data": + case strings.HasPrefix(contentType, "multipart/"): tag = "Multipart" - case "application/x-www-form-urlencoded": + case contentType == "application/x-www-form-urlencoded": tag = "Formdata" - case "text/plain": + case contentType == "text/plain": tag = "Text" default: bd := RequestBodyDefinition{ @@ -685,6 +692,8 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response switch contentType { case "application/json": tag = "JSON" + case "application/x-www-form-urlencoded": + tag = "Formdata" case "text/plain": tag = "Text" default: @@ -862,15 +871,15 @@ func GenerateGinServer(t *template.Template, operations []OperationDefinition) ( } func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Options) (string, error) { - templates := []string{"strict-interface.tmpl"} + templates := []string{"strict/strict-interface.tmpl"} if opts.GenerateChiServer { - templates = append(templates, "strict-chi.tmpl") + templates = append(templates, "strict/strict-chi.tmpl") } if opts.GenerateEchoServer { - templates = append(templates, "strict-echo.tmpl") + templates = append(templates, "strict/strict-echo.tmpl") } if opts.GenerateGinServer { - templates = append(templates, "strict-gin.tmpl") + templates = append(templates, "strict/strict-gin.tmpl") } return GenerateTemplates(templates, t, operations) } diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index 648532f08c..ffb62bbebe 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -133,11 +133,21 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) + {{if eq .NameTag "JSON" -}} + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + {{else if eq .NameTag "Formdata" -}} + bodyStr, err := runtime.MarshalForm(body, nil) + if err != nil { + return nil, err + } + bodyReader = strings.NewReader(bodyStr.Encode()) + {{else if eq .NameTag "Text" -}} + bodyReader = strings.NewReader(string(body)) + {{end -}} return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader) } {{end -}} diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl index 6584156201..7b6e2ce8fe 100644 --- a/pkg/codegen/templates/strict/strict-chi.tmpl +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -45,20 +45,26 @@ type strictHandler struct { http.Error(w, "can't decode formdata: " + err.Error(), http.StatusBadRequest) return } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &r.Form + var body {{$opid}}{{.NameTag}}RequestBody + if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil { + http.Error(w, "can't bind formdata: " + err.Error(), http.StatusBadRequest) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Multipart" -}} - if err := r.ParseMultipartForm(32 << 20); err != nil { + if reader, err := r.MultipartReader(); err != nil { http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest) return + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.MultipartForm {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "can't read body: " + err.Error(), http.StatusBadRequest) return } - body = {{$opid}}{{.NameTag}}RequestBody(data) + body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body @@ -81,7 +87,7 @@ type strictHandler struct { {{$fixedStatusCode := .HasFixedStatusCode -}} {{$headers := .Headers -}} {{range .Contents -}} - case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + case {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response: {{range $headers -}} w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} @@ -96,6 +102,12 @@ type strictHandler struct { writeJSON(w, v) {{else if eq .NameTag "Text" -}} writeRaw(w, ([]byte)(v)) + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm(v, nil); err != nil { + fmt.Fprintln(w, err) + } else { + writeRaw(w, []byte(form.Encode())) + } {{else -}} if closer, ok := v.Body.(io.ReadCloser); ok { defer closer.Close() diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl index b2976c9e77..3db5ea22a9 100644 --- a/pkg/codegen/templates/strict/strict-echo.tmpl +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -40,16 +40,20 @@ type strictHandler struct { } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Formdata" -}} - if body, err := ctx.FormParams(); err == nil { + if form, err := ctx.FormParams(); err == nil { + var body {{$opid}}{{.NameTag}}RequestBody + if err := runtime.BindForm(&body, form, nil, nil); err != nil { + return err + } request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body } else { return err } {{else if eq .NameTag "Multipart" -}} - if body, err := ctx.MultipartForm(); err == nil { - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body - } else { + if reader, err := ctx.Request().MultipartReader(); err != nil { return err + } else { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } {{else if eq .NameTag "Text" -}} data, err := ioutil.ReadAll(ctx.Request().Body) @@ -79,7 +83,7 @@ type strictHandler struct { {{$fixedStatusCode := .HasFixedStatusCode -}} {{$headers := .Headers -}} {{range .Contents -}} - case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + case {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response: {{range $headers -}} ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} @@ -87,6 +91,12 @@ type strictHandler struct { return ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) {{else if eq .NameTag "Text" -}} return ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(v)) + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm(v, nil); err != nil { + return err + } else { + return ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(form.Encode())) + } {{else -}} if v.ContentLength != 0 { ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl index e335286abb..5a1ebf585b 100644 --- a/pkg/codegen/templates/strict/strict-gin.tmpl +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -45,10 +45,15 @@ type strictHandler struct { ctx.Error(err) return } - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &ctx.Request.Form + var body {{$opid}}{{.NameTag}}RequestBody + if err := runtime.BindForm(&body, ctx.Request.Form, nil, nil); err != nil { + ctx.Error(err) + return + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else if eq .NameTag "Multipart" -}} - if body, err := ctx.MultipartForm(); err == nil { - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = body + if reader, err := ctx.Request.MultipartReader(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader } else { ctx.Error(err) return @@ -59,7 +64,7 @@ type strictHandler struct { ctx.Error(err) return } - body = {{$opid}}{{.NameTag}}RequestBody(data) + body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else -}} request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body @@ -82,14 +87,20 @@ type strictHandler struct { {{$fixedStatusCode := .HasFixedStatusCode -}} {{$headers := .Headers -}} {{range .Contents -}} - case {{$opid}}{{$statusCode}}{{.NameTag}}Response: + case {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response: {{range $headers -}} ctx.Header("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} {{if eq .NameTag "JSON" -}} ctx.JSON({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, v) {{else if eq .NameTag "Text" -}} - ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, {{.ContentType}}, []byte(v)) + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(v)) + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm(v, nil); err != nil { + ctx.Error(err) + } else { + ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(form.Encode())) + } {{else -}} if closer, ok := v.Body.(io.ReadCloser); ok { defer closer.Close() diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index fd22d67afc..463299024c 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -12,7 +12,7 @@ {{end -}} {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "JSON"}}*{{$opid}}{{.NameTag}}RequestBody{{else if eq .NameTag "Multipart"}}*multipart.Form{{else if eq .NameTag "Formdata"}}*url.Values{{else}}io.Reader{{end}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} {{end -}} } @@ -39,7 +39,7 @@ } {{end}} {{else -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response struct { + type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response struct { Body {{if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} Headers {{$opid}}{{$statusCode}}ResponseHeaders diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go index 37cc3d6c16..98a648b0e4 100644 --- a/pkg/runtime/bindform.go +++ b/pkg/runtime/bindform.go @@ -5,18 +5,24 @@ import ( "errors" "fmt" "mime/multipart" + "net/url" "reflect" "strconv" "strings" - "github.com/deepmap/oapi-codegen/pkg/codegen" "github.com/deepmap/oapi-codegen/pkg/types" ) const tagName = "json" const jsonContentType = "application/json" -func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]codegen.RequestBodyEncoding) error { +type RequestBodyEncoding struct { + ContentType string + Style string + Explode *bool +} + +func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]RequestBodyEncoding) error { ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) if ptrVal.Kind() != reflect.Struct { return errors.New("form data body should be a struct") @@ -64,6 +70,40 @@ func BindForm(ptr interface{}, form map[string][]string, files map[string][]*mul return nil } +func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url.Values, error) { + ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) + if ptrVal.Kind() != reflect.Struct { + return nil, errors.New("form data body should be a struct") + } + tValue := ptrVal.Type() + result := make(url.Values) + for i := 0; i < tValue.NumField(); i++ { + field := tValue.Field(i) + tag := field.Tag.Get(tagName) + if !field.IsExported() || tag == "-" { + continue + } + omitEmpty := strings.HasSuffix(tag, ",omitempty") + if omitEmpty && ptrVal.Field(i).IsZero() { + continue + } + tag = strings.Split(tag, ",")[0] // extract the name of the tag + if encoding, ok := encodings[tag]; ok && encoding.ContentType != "" { + if strings.HasPrefix(encoding.ContentType, jsonContentType) { + if data, err := json.Marshal(ptrVal.Field(i)); err != nil { + return nil, err + } else { + result[tag] = append(result[tag], string(data)) + } + } + return nil, errors.New("unsupported encoding, only application/json is supported") + } else { + marshalFormImpl(ptrVal.Field(i), result, tag) + } + } + return result, nil +} + func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { var hasData bool switch v.Kind() { @@ -174,6 +214,37 @@ func indexedElementsCount(form map[string][]string, files map[string][]*multipar } func bindAdditionalProperties(additionalProperties reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { - //TODO: support additional properties + // TODO: support additional properties return false, nil } + +func marshalFormImpl(v reflect.Value, result url.Values, name string) { + switch v.Kind() { + case reflect.Interface, reflect.Ptr: + marshalFormImpl(v.Elem(), result, name) + case reflect.Slice: + for i := 0; i < v.Len(); i++ { + elem := v.Index(i) + marshalFormImpl(elem, result, fmt.Sprintf("%s[%v]", name, i)) + } + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + field := v.Type().Field(i) + tag := field.Tag.Get(tagName) + if field.Name == "AdditionalProperties" && tag == "-" { + iter := v.MapRange() + for iter.Next() { + marshalFormImpl(iter.Value(), result, fmt.Sprintf("%s[%s]", name, iter.Key().String())) + } + continue + } + if !field.IsExported() || tag == "-" { + continue + } + tag = strings.Split(tag, ",")[0] // extract the name of the tag + marshalFormImpl(v.Field(i), result, fmt.Sprintf("%s[%s]", name, tag)) + } + default: + result[name] = append(result[name], fmt.Sprint(v.Interface())) + } +} diff --git a/pkg/runtime/bindform_test.go b/pkg/runtime/bindform_test.go index 14c6980f75..97a6af608d 100644 --- a/pkg/runtime/bindform_test.go +++ b/pkg/runtime/bindform_test.go @@ -116,6 +116,54 @@ func TestBindMultipartForm(t *testing.T) { assert.Equal(t, "123.pdf", (*testStruct.OptFiles)[2].Filename()) } +func TestMarshalForm(t *testing.T) { + type testSubStruct struct { + Int int `json:"int"` + String string `json:"string"` + } + type testStruct struct { + Int int `json:"int,omitempty"` + Bool bool `json:"bool,omitempty"` + String string `json:"string,omitempty"` + IntSlice []int `json:"int_slice,omitempty"` + Struct testSubStruct `json:"struct,omitempty"` + StructSlice []testSubStruct `json:"struct_slice,omitempty"` + OptInt *int `json:"opt_int,omitempty"` + OptBool *bool `json:"opt_bool,omitempty"` + OptString *string `json:"opt_string,omitempty"` + OptStruct *testSubStruct `json:"opt_struct,omitempty"` + OptStructSlice *[]testSubStruct `json:"opt_struct_slice,omitempty"` + NotSerializable int `json:"-"` + unexported int + } + + testCases := map[string]testStruct{ + "int=123": {Int: 123}, + "bool=true": {Bool: true}, + "string=example": {String: "example"}, + "int_slice[0]=1&int_slice[1]=2&int_slice[2]=3": {IntSlice: []int{1, 2, 3}}, + "struct[int]=789&struct[string]=abc": {Struct: testSubStruct{Int: 789, String: "abc"}}, + "struct_slice[0][int]=3&struct_slice[0][string]=a&struct_slice[1][int]=2&struct_slice[1][string]=b&struct_slice[2][int]=1&struct_slice[2][string]=c": { + StructSlice: []testSubStruct{{Int: 3, String: "a"}, {Int: 2, String: "b"}, {Int: 1, String: "c"}}, + }, + "opt_int=456": {OptInt: func(v int) *int { return &v }(456)}, + "opt_bool=true": {OptBool: func(v bool) *bool { return &v }(true)}, + "opt_string=def": {OptString: func(v string) *string { return &v }("def")}, + "opt_struct[int]=456&opt_struct[string]=def": {OptStruct: &testSubStruct{Int: 456, String: "def"}}, + "opt_struct_slice[0][int]=123&opt_struct_slice[0][string]=abc&opt_struct_slice[1][int]=456&opt_struct_slice[1][string]=def": { + OptStructSlice: &([]testSubStruct{{Int: 123, String: "abc"}, {Int: 456, String: "def"}}), + }, + } + + for k, v := range testCases { + marshalled, err := MarshalForm(v, nil) + assert.NoError(t, err) + encoded, err := url.QueryUnescape(marshalled.Encode()) + assert.NoError(t, err) + assert.Equal(t, k, encoded) + } +} + type fileData struct { field string filename string From 7df598b59c56f4bcda8816dcb1ff46b8e2f68f2f Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Wed, 20 Apr 2022 18:16:40 +0300 Subject: [PATCH 09/19] Fixed incorrect referencing of request bodies, added sorting of response definitions --- .../authenticated-api/echo/api/api.gen.go | 5 +- .../petstore-expanded/chi/api/petstore.gen.go | 5 +- .../echo/api/petstore-types.gen.go | 5 +- .../petstore-expanded/petstore-client.gen.go | 5 +- .../strict/api/petstore.gen.go | 5 +- examples/strict-server/chi/server.gen.go | 62 ++++------- examples/strict-server/client/client.gen.go | 30 ++---- examples/strict-server/echo/server.gen.go | 100 +++++++----------- examples/strict-server/gin/server.gen.go | 88 ++++++--------- internal/test/client/client.gen.go | 10 +- internal/test/components/components.gen.go | 12 ++- internal/test/issues/issue-312/issue.gen.go | 5 +- internal/test/schemas/schemas.gen.go | 5 +- internal/test/server/server.gen.go | 10 +- pkg/codegen/operations.go | 9 +- 15 files changed, 130 insertions(+), 226 deletions(-) diff --git a/examples/authenticated-api/echo/api/api.gen.go b/examples/authenticated-api/echo/api/api.gen.go index 29d6ae403b..f4b397a0c4 100644 --- a/examples/authenticated-api/echo/api/api.gen.go +++ b/examples/authenticated-api/echo/api/api.gen.go @@ -47,11 +47,8 @@ type ThingWithID struct { Id int64 `json:"id"` } -// AddThingJSONBody defines parameters for AddThing. -type AddThingJSONBody Thing - // AddThingJSONRequestBody defines body for AddThing for application/json ContentType. -type AddThingJSONRequestBody AddThingJSONBody +type AddThingJSONRequestBody Thing // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go index be839b0023..8e337f5552 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/api/petstore.gen.go @@ -54,11 +54,8 @@ type FindPetsParams struct { Limit *int32 `json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody AddPetJSONBody +type AddPetJSONRequestBody NewPet // ServerInterface represents all server handlers. type ServerInterface interface { diff --git a/examples/petstore-expanded/echo/api/petstore-types.gen.go b/examples/petstore-expanded/echo/api/petstore-types.gen.go index 24add64187..1dada661d5 100644 --- a/examples/petstore-expanded/echo/api/petstore-types.gen.go +++ b/examples/petstore-expanded/echo/api/petstore-types.gen.go @@ -39,8 +39,5 @@ type FindPetsParams struct { Limit *int32 `json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody AddPetJSONBody +type AddPetJSONRequestBody NewPet diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go index 9c163ed0d7..a5cefdc79b 100644 --- a/examples/petstore-expanded/petstore-client.gen.go +++ b/examples/petstore-expanded/petstore-client.gen.go @@ -53,11 +53,8 @@ type FindPetsParams struct { Limit *int32 `json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody AddPetJSONBody +type AddPetJSONRequestBody NewPet // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/petstore-expanded/strict/api/petstore.gen.go b/examples/petstore-expanded/strict/api/petstore.gen.go index ad03e9f5dc..263e7f5486 100644 --- a/examples/petstore-expanded/strict/api/petstore.gen.go +++ b/examples/petstore-expanded/strict/api/petstore.gen.go @@ -56,11 +56,8 @@ type FindPetsParams struct { Limit *int32 `json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody AddPetJSONBody +type AddPetJSONRequestBody NewPet // ServerInterface represents all server handlers. type ServerInterface interface { diff --git a/examples/strict-server/chi/server.gen.go b/examples/strict-server/chi/server.gen.go index 8540398d89..6a6f5f1fcd 100644 --- a/examples/strict-server/chi/server.gen.go +++ b/examples/strict-server/chi/server.gen.go @@ -31,44 +31,26 @@ type Example struct { Value *string `json:"value,omitempty"` } -// JSONExampleJSONBody defines parameters for JSONExample. -type JSONExampleJSONBody Example - -// MultipartExampleMultipartBody defines parameters for MultipartExample. -type MultipartExampleMultipartBody Example - -// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesJSONBody Example - -// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesFormdataBody Example - -// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesMultipartBody Example - // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string -// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. -type URLEncodedExampleFormdataBody Example - // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody JSONExampleJSONBody +type JSONExampleJSONRequestBody Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody +type MultipartExampleMultipartRequestBody Example // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody +type MultipleRequestAndResponseTypesJSONRequestBody Example // MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody +type MultipleRequestAndResponseTypesFormdataRequestBody Example // MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody +type MultipleRequestAndResponseTypesMultipartRequestBody Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody @@ -77,7 +59,7 @@ type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTy type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody +type URLEncodedExampleFormdataRequestBody Example // ServerInterface represents all server handlers. type ServerInterface interface { @@ -382,12 +364,6 @@ type MultipleRequestAndResponseTypesRequestObject struct { TextBody *MultipleRequestAndResponseTypesTextRequestBody } -type MultipleRequestAndResponseTypes200TextResponse string - -func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type MultipleRequestAndResponseTypes200JSONResponse Example func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { @@ -410,6 +386,12 @@ type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { ContentLength int64 } +type MultipleRequestAndResponseTypes200TextResponse string + +func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -453,10 +435,6 @@ type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } -type URLEncodedExampledefaultResponse struct { - StatusCode int -} - type URLEncodedExample200FormdataResponse Example func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { @@ -469,6 +447,10 @@ func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Badrequest)(t)) } +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { @@ -643,10 +625,6 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, response := handler(r.Context(), w, r, request) switch v := response.(type) { - case MultipleRequestAndResponseTypes200TextResponse: - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(200) - writeRaw(w, ([]byte)(v)) case MultipleRequestAndResponseTypes200JSONResponse: w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) @@ -679,6 +657,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, defer closer.Close() } _, _ = io.Copy(w, v.Body) + case MultipleRequestAndResponseTypes200TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + writeRaw(w, ([]byte)(v)) case error: http.Error(w, v.Error(), http.StatusInternalServerError) case nil: @@ -792,8 +774,6 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques response := handler(r.Context(), w, r, request) switch v := response.(type) { - case URLEncodedExampledefaultResponse: - w.WriteHeader(v.StatusCode) case URLEncodedExample200FormdataResponse: w.Header().Set("Content-Type", "application/x-www-form-urlencoded") w.WriteHeader(200) @@ -806,6 +786,8 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques w.Header().Set("Content-Type", "text/plain") w.WriteHeader(400) writeRaw(w, ([]byte)(v)) + case URLEncodedExampledefaultResponse: + w.WriteHeader(v.StatusCode) case error: http.Error(w, v.Error(), http.StatusInternalServerError) case nil: diff --git a/examples/strict-server/client/client.gen.go b/examples/strict-server/client/client.gen.go index b989d02dca..88fd403c87 100644 --- a/examples/strict-server/client/client.gen.go +++ b/examples/strict-server/client/client.gen.go @@ -25,44 +25,26 @@ type Example struct { Value *string `json:"value,omitempty"` } -// JSONExampleJSONBody defines parameters for JSONExample. -type JSONExampleJSONBody Example - -// MultipartExampleMultipartBody defines parameters for MultipartExample. -type MultipartExampleMultipartBody Example - -// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesMultipartBody Example - // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody string -// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesJSONBody Example - -// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesFormdataBody Example - // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string -// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. -type URLEncodedExampleFormdataBody Example - // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody JSONExampleJSONBody +type JSONExampleJSONRequestBody Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody +type MultipartExampleMultipartRequestBody Example // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody +type MultipleRequestAndResponseTypesJSONRequestBody Example // MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody +type MultipleRequestAndResponseTypesFormdataRequestBody Example // MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody +type MultipleRequestAndResponseTypesMultipartRequestBody Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody @@ -71,7 +53,7 @@ type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTy type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody +type URLEncodedExampleFormdataRequestBody Example // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go index c37163042c..0866994c0b 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/examples/strict-server/echo/server.gen.go @@ -30,44 +30,26 @@ type Example struct { Value *string `json:"value,omitempty"` } -// JSONExampleJSONBody defines parameters for JSONExample. -type JSONExampleJSONBody Example - -// MultipartExampleMultipartBody defines parameters for MultipartExample. -type MultipartExampleMultipartBody Example - -// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesFormdataBody Example - -// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesMultipartBody Example - // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody string -// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesJSONBody Example - // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string -// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. -type URLEncodedExampleFormdataBody Example - // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody JSONExampleJSONBody +type JSONExampleJSONRequestBody Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody +type MultipartExampleMultipartRequestBody Example // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody +type MultipleRequestAndResponseTypesJSONRequestBody Example // MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody +type MultipleRequestAndResponseTypesFormdataRequestBody Example // MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody +type MultipleRequestAndResponseTypesMultipartRequestBody Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody @@ -76,7 +58,7 @@ type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTy type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody +type URLEncodedExampleFormdataRequestBody Example // ServerInterface represents all server handlers. type ServerInterface interface { @@ -243,11 +225,6 @@ type MultipleRequestAndResponseTypesRequestObject struct { TextBody *MultipleRequestAndResponseTypesTextRequestBody } -type MultipleRequestAndResponseTypes200ImagepngResponse struct { - Body io.Reader - ContentLength int64 -} - type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { Body io.Reader ContentLength int64 @@ -271,12 +248,13 @@ func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byt return json.Marshal((Example)(t)) } -type TextExampleRequestObject struct { - Body *TextExampleTextRequestBody +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 } -type TextExampledefaultResponse struct { - StatusCode int +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody } type TextExample200TextResponse string @@ -291,12 +269,12 @@ func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Badrequest)(t)) } -type UnknownExampleRequestObject struct { - Body io.Reader +type TextExampledefaultResponse struct { + StatusCode int } -type UnknownExampledefaultResponse struct { - StatusCode int +type UnknownExampleRequestObject struct { + Body io.Reader } type UnknownExample200Videomp4Response struct { @@ -310,10 +288,20 @@ func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Badrequest)(t)) } +type UnknownExampledefaultResponse struct { + StatusCode int +} + type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } +type URLEncodedExample200FormdataResponse Example + +func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + type URLEncodedExample400TextResponse Badrequest func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { @@ -324,12 +312,6 @@ type URLEncodedExampledefaultResponse struct { StatusCode int } -type URLEncodedExample200FormdataResponse Example - -func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - // StrictServerInterface represents all server handlers. type StrictServerInterface interface { @@ -492,14 +474,6 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error response := handler(ctx, request) switch v := response.(type) { - case MultipleRequestAndResponseTypes200ImagepngResponse: - if v.ContentLength != 0 { - ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() - } - return ctx.Stream(200, "image/png", v.Body) case MultipleRequestAndResponseTypes200MultipartformDataResponse: if v.ContentLength != 0 { ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) @@ -518,6 +492,14 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error } else { return ctx.Blob(200, "application/x-www-form-urlencoded", []byte(form.Encode())) } + case MultipleRequestAndResponseTypes200ImagepngResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "image/png", v.Body) case error: return v case nil: @@ -548,12 +530,12 @@ func (sh *strictHandler) TextExample(ctx echo.Context) error { response := handler(ctx, request) switch v := response.(type) { - case TextExampledefaultResponse: - return ctx.NoContent(v.StatusCode) case TextExample200TextResponse: return ctx.Blob(200, "text/plain", []byte(v)) case TextExample400TextResponse: return ctx.Blob(400, "text/plain", []byte(v)) + case TextExampledefaultResponse: + return ctx.NoContent(v.StatusCode) case error: return v case nil: @@ -579,8 +561,6 @@ func (sh *strictHandler) UnknownExample(ctx echo.Context) error { response := handler(ctx, request) switch v := response.(type) { - case UnknownExampledefaultResponse: - return ctx.NoContent(v.StatusCode) case UnknownExample200Videomp4Response: if v.ContentLength != 0 { ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) @@ -591,6 +571,8 @@ func (sh *strictHandler) UnknownExample(ctx echo.Context) error { return ctx.Stream(200, "video/mp4", v.Body) case UnknownExample400TextResponse: return ctx.Blob(400, "text/plain", []byte(v)) + case UnknownExampledefaultResponse: + return ctx.NoContent(v.StatusCode) case error: return v case nil: @@ -624,16 +606,16 @@ func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { response := handler(ctx, request) switch v := response.(type) { - case URLEncodedExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) - case URLEncodedExampledefaultResponse: - return ctx.NoContent(v.StatusCode) case URLEncodedExample200FormdataResponse: if form, err := runtime.MarshalForm(v, nil); err != nil { return err } else { return ctx.Blob(200, "application/x-www-form-urlencoded", []byte(form.Encode())) } + case URLEncodedExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case URLEncodedExampledefaultResponse: + return ctx.NoContent(v.StatusCode) case error: return v case nil: diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go index b461f33d70..6eaf0d3e7b 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/examples/strict-server/gin/server.gen.go @@ -30,44 +30,26 @@ type Example struct { Value *string `json:"value,omitempty"` } -// JSONExampleJSONBody defines parameters for JSONExample. -type JSONExampleJSONBody Example - -// MultipartExampleMultipartBody defines parameters for MultipartExample. -type MultipartExampleMultipartBody Example - -// MultipleRequestAndResponseTypesJSONBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesJSONBody Example - -// MultipleRequestAndResponseTypesFormdataBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesFormdataBody Example - -// MultipleRequestAndResponseTypesMultipartBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesMultipartBody Example - // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string -// URLEncodedExampleFormdataBody defines parameters for URLEncodedExample. -type URLEncodedExampleFormdataBody Example - // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody JSONExampleJSONBody +type JSONExampleJSONRequestBody Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody MultipartExampleMultipartBody +type MultipartExampleMultipartRequestBody Example // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody MultipleRequestAndResponseTypesJSONBody +type MultipleRequestAndResponseTypesJSONRequestBody Example // MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody MultipleRequestAndResponseTypesFormdataBody +type MultipleRequestAndResponseTypesFormdataRequestBody Example // MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody MultipleRequestAndResponseTypesMultipartBody +type MultipleRequestAndResponseTypesMultipartRequestBody Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody @@ -76,7 +58,7 @@ type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTy type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody URLEncodedExampleFormdataBody +type URLEncodedExampleFormdataRequestBody Example // ServerInterface represents all server handlers. type ServerInterface interface { @@ -248,6 +230,17 @@ type MultipleRequestAndResponseTypesRequestObject struct { TextBody *MultipleRequestAndResponseTypesTextRequestBody } +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { Body io.Reader ContentLength int64 @@ -265,17 +258,6 @@ func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, e return json.Marshal((Example)(t)) } -type MultipleRequestAndResponseTypes200FormdataResponse Example - -func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - -type MultipleRequestAndResponseTypes200ImagepngResponse struct { - Body io.Reader - ContentLength int64 -} - type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -300,6 +282,11 @@ type UnknownExampleRequestObject struct { Body io.Reader } +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + type UnknownExample400TextResponse Badrequest func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { @@ -310,11 +297,6 @@ type UnknownExampledefaultResponse struct { StatusCode int } -type UnknownExample200Videomp4Response struct { - Body io.Reader - ContentLength int64 -} - type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } @@ -498,15 +480,6 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { response := handler(ctx, request) switch v := response.(type) { - case MultipleRequestAndResponseTypes200MultipartformDataResponse: - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() - } - ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) - case MultipleRequestAndResponseTypes200TextResponse: - ctx.Data(200, "text/plain", []byte(v)) - case MultipleRequestAndResponseTypes200JSONResponse: - ctx.JSON(200, v) case MultipleRequestAndResponseTypes200FormdataResponse: if form, err := runtime.MarshalForm(v, nil); err != nil { ctx.Error(err) @@ -518,6 +491,15 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { defer closer.Close() } ctx.DataFromReader(200, v.ContentLength, "image/png", v.Body, nil) + case MultipleRequestAndResponseTypes200MultipartformDataResponse: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) + case MultipleRequestAndResponseTypes200TextResponse: + ctx.Data(200, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes200JSONResponse: + ctx.JSON(200, v) case error: ctx.Error(v) case nil: @@ -578,15 +560,15 @@ func (sh *strictHandler) UnknownExample(ctx *gin.Context) { response := handler(ctx, request) switch v := response.(type) { - case UnknownExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) - case UnknownExampledefaultResponse: - ctx.Status(v.StatusCode) case UnknownExample200Videomp4Response: if closer, ok := v.Body.(io.ReadCloser); ok { defer closer.Close() } ctx.DataFromReader(200, v.ContentLength, "video/mp4", v.Body, nil) + case UnknownExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case UnknownExampledefaultResponse: + ctx.Status(v.StatusCode) case error: ctx.Error(v) case nil: diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index abea3b4db2..7b6f913dda 100644 --- a/internal/test/client/client.gen.go +++ b/internal/test/client/client.gen.go @@ -31,17 +31,11 @@ type SchemaObject struct { Role string `json:"role"` } -// PostBothJSONBody defines parameters for PostBoth. -type PostBothJSONBody SchemaObject - -// PostJsonJSONBody defines parameters for PostJson. -type PostJsonJSONBody SchemaObject - // PostBothJSONRequestBody defines body for PostBoth for application/json ContentType. -type PostBothJSONRequestBody PostBothJSONBody +type PostBothJSONRequestBody SchemaObject // PostJsonJSONRequestBody defines body for PostJson for application/json ContentType. -type PostJsonJSONRequestBody PostJsonJSONBody +type PostJsonJSONRequestBody SchemaObject // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 5483fafe10..04caee9974 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -88,6 +88,14 @@ type RequestBody struct { Field SchemaObject `json:"Field"` } +// EnsureEverythingIsReferencedJSONBody defines parameters for EnsureEverythingIsReferenced. +type EnsureEverythingIsReferencedJSONBody struct { + Field SchemaObject `json:"Field"` +} + +// EnsureEverythingIsReferencedTextBody defines parameters for EnsureEverythingIsReferenced. +type EnsureEverythingIsReferencedTextBody string + // ParamsWithAddPropsParams_P1 defines parameters for ParamsWithAddProps. type ParamsWithAddPropsParams_P1 struct { AdditionalProperties map[string]interface{} `json:"-"` @@ -123,10 +131,10 @@ type BodyWithAddPropsJSONBody_Inner struct { } // EnsureEverythingIsReferencedJSONRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. -type EnsureEverythingIsReferencedJSONRequestBody RequestBody +type EnsureEverythingIsReferencedJSONRequestBody EnsureEverythingIsReferencedJSONBody // EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for text/plain ContentType. -type EnsureEverythingIsReferencedTextRequestBody RequestBody +type EnsureEverythingIsReferencedTextRequestBody EnsureEverythingIsReferencedTextBody // BodyWithAddPropsJSONRequestBody defines body for BodyWithAddProps for application/json ContentType. type BodyWithAddPropsJSONRequestBody BodyWithAddPropsJSONBody diff --git a/internal/test/issues/issue-312/issue.gen.go b/internal/test/issues/issue-312/issue.gen.go index 4710f7ce95..a55d368906 100644 --- a/internal/test/issues/issue-312/issue.gen.go +++ b/internal/test/issues/issue-312/issue.gen.go @@ -43,11 +43,8 @@ type PetNames struct { Names []string `json:"names"` } -// ValidatePetsJSONBody defines parameters for ValidatePets. -type ValidatePetsJSONBody PetNames - // ValidatePetsJSONRequestBody defines body for ValidatePets for application/json ContentType. -type ValidatePetsJSONRequestBody ValidatePetsJSONBody +type ValidatePetsJSONRequestBody PetNames // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go index 84ff520038..bda527dd5c 100644 --- a/internal/test/schemas/schemas.gen.go +++ b/internal/test/schemas/schemas.gen.go @@ -72,9 +72,6 @@ type NullableProperties struct { // StringInPath defines model for StringInPath. type StringInPath string -// Issue185JSONBody defines parameters for Issue185. -type Issue185JSONBody NullableProperties - // Issue9JSONBody defines parameters for Issue9. type Issue9JSONBody interface{} @@ -84,7 +81,7 @@ type Issue9Params struct { } // Issue185JSONRequestBody defines body for Issue185 for application/json ContentType. -type Issue185JSONRequestBody Issue185JSONBody +type Issue185JSONRequestBody NullableProperties // Issue9JSONRequestBody defines body for Issue9 for application/json ContentType. type Issue9JSONRequestBody Issue9JSONBody diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go index aa881693a2..b4f2f72ef8 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -100,12 +100,6 @@ type GetWithArgsParams struct { // GetWithContentTypeParamsContentType defines parameters for GetWithContentType. type GetWithContentTypeParamsContentType string -// CreateResourceJSONBody defines parameters for CreateResource. -type CreateResourceJSONBody EveryTypeRequired - -// CreateResource2JSONBody defines parameters for CreateResource2. -type CreateResource2JSONBody Resource - // CreateResource2Params defines parameters for CreateResource2. type CreateResource2Params struct { // Some query argument @@ -119,10 +113,10 @@ type UpdateResource3JSONBody struct { } // CreateResourceJSONRequestBody defines body for CreateResource for application/json ContentType. -type CreateResourceJSONRequestBody CreateResourceJSONBody +type CreateResourceJSONRequestBody EveryTypeRequired // CreateResource2JSONRequestBody defines body for CreateResource2 for application/json ContentType. -type CreateResource2JSONRequestBody CreateResource2JSONBody +type CreateResource2JSONRequestBody Resource // UpdateResource3JSONRequestBody defines body for UpdateResource3 for application/json ContentType. type UpdateResource3JSONRequestBody UpdateResource3JSONBody diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 6ae008f58c..4cc0c90df9 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -630,11 +630,11 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody } // If the body is a pre-defined type - if IsGoTypeReference(bodyOrRef.Ref) { + if IsGoTypeReference(content.Schema.Ref) { // Convert the reference path to Go type - refType, err := RefPathToGoType(bodyOrRef.Ref) + refType, err := RefPathToGoType(content.Schema.Ref) if err != nil { - return nil, nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", bodyOrRef.Ref, err) + return nil, nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", content.Schema.Ref, err) } bodySchema.RefType = refType } @@ -679,7 +679,8 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody func GenerateResponseDefinitions(operationID string, responses openapi3.Responses) ([]ResponseDefinition, error) { var responseDefinitions []ResponseDefinition - for statusCode, responseOrRef := range responses { + for _, statusCode := range SortedResponsesKeys(responses) { + responseOrRef := responses[statusCode] if responseOrRef == nil { continue } From bed3a46ea8642eb7ac68ee17080f77f8f3edc769 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Wed, 20 Apr 2022 18:20:45 +0300 Subject: [PATCH 10/19] Added sorting of content types in responses --- examples/strict-server/echo/server.gen.go | 42 +++++++++++------------ examples/strict-server/gin/server.gen.go | 16 ++++----- pkg/codegen/operations.go | 3 +- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go index 0866994c0b..488bcd2700 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/examples/strict-server/echo/server.gen.go @@ -225,17 +225,6 @@ type MultipleRequestAndResponseTypesRequestObject struct { TextBody *MultipleRequestAndResponseTypesTextRequestBody } -type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} - -type MultipleRequestAndResponseTypes200TextResponse string - -func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type MultipleRequestAndResponseTypes200JSONResponse Example func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { @@ -253,6 +242,17 @@ type MultipleRequestAndResponseTypes200ImagepngResponse struct { ContentLength int64 } +type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((string)(t)) +} + type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -474,16 +474,6 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error response := handler(ctx, request) switch v := response.(type) { - case MultipleRequestAndResponseTypes200MultipartformDataResponse: - if v.ContentLength != 0 { - ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() - } - return ctx.Stream(200, "multipart/form-data", v.Body) - case MultipleRequestAndResponseTypes200TextResponse: - return ctx.Blob(200, "text/plain", []byte(v)) case MultipleRequestAndResponseTypes200JSONResponse: return ctx.JSON(200, v) case MultipleRequestAndResponseTypes200FormdataResponse: @@ -500,6 +490,16 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error defer closer.Close() } return ctx.Stream(200, "image/png", v.Body) + case MultipleRequestAndResponseTypes200MultipartformDataResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, "multipart/form-data", v.Body) + case MultipleRequestAndResponseTypes200TextResponse: + return ctx.Blob(200, "text/plain", []byte(v)) case error: return v case nil: diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go index 6eaf0d3e7b..9d98091174 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/examples/strict-server/gin/server.gen.go @@ -230,6 +230,12 @@ type MultipleRequestAndResponseTypesRequestObject struct { TextBody *MultipleRequestAndResponseTypesTextRequestBody } +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + type MultipleRequestAndResponseTypes200FormdataResponse Example func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { @@ -252,12 +258,6 @@ func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, e return json.Marshal((string)(t)) } -type MultipleRequestAndResponseTypes200JSONResponse Example - -func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -480,6 +480,8 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { response := handler(ctx, request) switch v := response.(type) { + case MultipleRequestAndResponseTypes200JSONResponse: + ctx.JSON(200, v) case MultipleRequestAndResponseTypes200FormdataResponse: if form, err := runtime.MarshalForm(v, nil); err != nil { ctx.Error(err) @@ -498,8 +500,6 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) case MultipleRequestAndResponseTypes200TextResponse: ctx.Data(200, "text/plain", []byte(v)) - case MultipleRequestAndResponseTypes200JSONResponse: - ctx.JSON(200, v) case error: ctx.Error(v) case nil: diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 4cc0c90df9..9a2edbc8ea 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -688,7 +688,8 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response var responseContentDefinitions []ResponseContentDefinition - for contentType, content := range response.Content { + for _, contentType := range SortedContentKeys(response.Content) { + content := response.Content[contentType] var tag string switch contentType { case "application/json": From 5c149546cf0fb96ddbaf52a5d7fe2f4fa88bd4d1 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 24 Apr 2022 18:00:27 +0300 Subject: [PATCH 11/19] Support multipart responses using callback function, added header example --- examples/strict-server/chi/server.gen.go | 244 +++++++++++++----- examples/strict-server/client/client.gen.go | 168 ++++++++++++ examples/strict-server/echo/server.gen.go | 219 +++++++++++----- examples/strict-server/gin/server.gen.go | 226 +++++++++++----- examples/strict-server/strict-schema.yaml | 38 +++ pkg/codegen/operations.go | 10 +- pkg/codegen/templates/strict/strict-chi.tmpl | 10 +- pkg/codegen/templates/strict/strict-echo.tmpl | 7 + pkg/codegen/templates/strict/strict-gin.tmpl | 7 + .../templates/strict/strict-interface.tmpl | 12 +- pkg/runtime/bindform.go | 26 +- 11 files changed, 746 insertions(+), 221 deletions(-) diff --git a/examples/strict-server/chi/server.gen.go b/examples/strict-server/chi/server.gen.go index 6a6f5f1fcd..69d738e971 100644 --- a/examples/strict-server/chi/server.gen.go +++ b/examples/strict-server/chi/server.gen.go @@ -37,6 +37,12 @@ type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody Example @@ -61,6 +67,9 @@ type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. type URLEncodedExampleFormdataRequestBody Example +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody Example + // ServerInterface represents all server handlers. type ServerInterface interface { @@ -81,6 +90,9 @@ type ServerInterface interface { // (POST /urlencoded) URLEncodedExample(w http.ResponseWriter, r *http.Request) + + // (POST /with-headers) + HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) } // ServerInterfaceWrapper converts contexts to parameters. @@ -182,6 +194,70 @@ func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *h handler(w, r.WithContext(ctx)) } +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := r.Header + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header1", Count: n}) + return + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header1", Err: err}) + return + } + + params.Header1 = Header1 + + } else { + err := fmt.Errorf("Header parameter header1 is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "header1", Err: err}) + return + } + + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "header2", Count: n}) + return + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "header2", Err: err}) + return + } + + params.Header2 = &Header2 + + } + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.HeadersExample(w, r, params) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + type UnescapedCookieParamError struct { ParamName string Err error @@ -313,6 +389,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/with-headers", wrapper.HeadersExample) + }) return r } @@ -329,10 +408,6 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { type JSONExample400TextResponse Badrequest -func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type JSONExampledefaultResponse struct { StatusCode int } @@ -341,17 +416,10 @@ type MultipartExampleRequestObject struct { Body *multipart.Reader } -type MultipartExample200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error type MultipartExample400TextResponse Badrequest -func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type MultipartExampledefaultResponse struct { StatusCode int } @@ -372,42 +440,23 @@ func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, e type MultipleRequestAndResponseTypes200FormdataResponse Example -func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - type MultipleRequestAndResponseTypes200ImagepngResponse struct { Body io.Reader ContentLength int64 } -type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error type MultipleRequestAndResponseTypes200TextResponse string -func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } type TextExample200TextResponse string -func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExample400TextResponse Badrequest -func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type TextExampledefaultResponse struct { StatusCode int } @@ -423,10 +472,6 @@ type UnknownExample200Videomp4Response struct { type UnknownExample400TextResponse Badrequest -func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type UnknownExampledefaultResponse struct { StatusCode int } @@ -437,17 +482,34 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) +type URLEncodedExample400TextResponse Badrequest + +type URLEncodedExampledefaultResponse struct { + StatusCode int } -type URLEncodedExample400TextResponse Badrequest +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} -func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int } -type URLEncodedExampledefaultResponse struct { +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400TextResponse Badrequest + +type HeadersExampledefaultResponse struct { StatusCode int } @@ -471,6 +533,9 @@ type StrictServerInterface interface { // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} } type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) interface{} @@ -546,16 +611,14 @@ func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request response := handler(r.Context(), w, r, request) switch v := response.(type) { - case MultipartExample200MultipartformDataResponse: - w.Header().Set("Content-Type", "multipart/form-data") - if v.ContentLength != 0 { - w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } + case MultipartExample200MultipartResponse: + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) w.WriteHeader(200) - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + defer writer.Close() + if err := v(writer); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) } - _, _ = io.Copy(w, v.Body) case MultipartExample400TextResponse: w.Header().Set("Content-Type", "text/plain") w.WriteHeader(400) @@ -647,16 +710,14 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, defer closer.Close() } _, _ = io.Copy(w, v.Body) - case MultipleRequestAndResponseTypes200MultipartformDataResponse: - w.Header().Set("Content-Type", "multipart/form-data") - if v.ContentLength != 0 { - w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } + case MultipleRequestAndResponseTypes200MultipartResponse: + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) w.WriteHeader(200) - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + defer writer.Close() + if err := v(writer); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) } - _, _ = io.Copy(w, v.Body) case MultipleRequestAndResponseTypes200TextResponse: w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) @@ -796,6 +857,49 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques } } +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case HeadersExample200JSONResponse: + w.Header().Set("header1", fmt.Sprint(v.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(v.Headers.Header2)) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case HeadersExample400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case HeadersExampledefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + func writeJSON(w http.ResponseWriter, v interface{}) { if err := json.NewEncoder(w).Encode(v); err != nil { fmt.Fprintln(w, err) @@ -811,17 +915,19 @@ func writeRaw(w http.ResponseWriter, b []byte) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", - "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", - "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", - "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", - "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", - "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", - "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", - "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", - "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", - "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", - "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", + "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", + "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", + "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", + "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", + "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", + "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", + "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", + "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", + "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", + "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", + "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", + "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", + "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/client/client.gen.go b/examples/strict-server/client/client.gen.go index 88fd403c87..87353cafbc 100644 --- a/examples/strict-server/client/client.gen.go +++ b/examples/strict-server/client/client.gen.go @@ -31,6 +31,12 @@ type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody Example @@ -55,6 +61,9 @@ type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. type URLEncodedExampleFormdataRequestBody Example +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody Example + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -157,6 +166,11 @@ type ClientInterface interface { URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) URLEncodedExampleWithFormdataBody(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // HeadersExample request with any body + HeadersExampleWithBody(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + HeadersExample(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) JSONExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -303,6 +317,30 @@ func (c *Client) URLEncodedExampleWithFormdataBody(ctx context.Context, body URL return c.Client.Do(req) } +func (c *Client) HeadersExampleWithBody(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadersExampleRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) HeadersExample(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadersExampleRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // NewJSONExampleRequest calls the generic JSONExample builder with application/json body func NewJSONExampleRequest(server string, body JSONExampleJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -535,6 +573,66 @@ func NewURLEncodedExampleRequestWithBody(server string, contentType string, body return req, nil } +// NewHeadersExampleRequest calls the generic HeadersExample builder with application/json body +func NewHeadersExampleRequest(server string, params *HeadersExampleParams, body HeadersExampleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewHeadersExampleRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewHeadersExampleRequestWithBody generates requests for HeadersExample with any type of body +func NewHeadersExampleRequestWithBody(server string, params *HeadersExampleParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/with-headers") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "header1", runtime.ParamLocationHeader, params.Header1) + if err != nil { + return nil, err + } + + req.Header.Set("header1", headerParam0) + + if params.Header2 != nil { + var headerParam1 string + + headerParam1, err = runtime.StyleParamWithLocation("simple", false, "header2", runtime.ParamLocationHeader, *params.Header2) + if err != nil { + return nil, err + } + + req.Header.Set("header2", headerParam1) + } + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -607,6 +705,11 @@ type ClientWithResponsesInterface interface { URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) URLEncodedExampleWithFormdataBodyWithResponse(ctx context.Context, body URLEncodedExampleFormdataRequestBody, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) + + // HeadersExample request with any body + HeadersExampleWithBodyWithResponse(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) + + HeadersExampleWithResponse(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) } type JSONExampleResponse struct { @@ -737,6 +840,28 @@ func (r URLEncodedExampleResponse) StatusCode() int { return 0 } +type HeadersExampleResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Example +} + +// Status returns HTTPResponse.Status +func (r HeadersExampleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r HeadersExampleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + // JSONExampleWithBodyWithResponse request with arbitrary body returning *JSONExampleResponse func (c *ClientWithResponses) JSONExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*JSONExampleResponse, error) { rsp, err := c.JSONExampleWithBody(ctx, contentType, body, reqEditors...) @@ -839,6 +964,23 @@ func (c *ClientWithResponses) URLEncodedExampleWithFormdataBodyWithResponse(ctx return ParseURLEncodedExampleResponse(rsp) } +// HeadersExampleWithBodyWithResponse request with arbitrary body returning *HeadersExampleResponse +func (c *ClientWithResponses) HeadersExampleWithBodyWithResponse(ctx context.Context, params *HeadersExampleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) { + rsp, err := c.HeadersExampleWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseHeadersExampleResponse(rsp) +} + +func (c *ClientWithResponses) HeadersExampleWithResponse(ctx context.Context, params *HeadersExampleParams, body HeadersExampleJSONRequestBody, reqEditors ...RequestEditorFn) (*HeadersExampleResponse, error) { + rsp, err := c.HeadersExample(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseHeadersExampleResponse(rsp) +} + // ParseJSONExampleResponse parses an HTTP response from a JSONExampleWithResponse call func ParseJSONExampleResponse(rsp *http.Response) (*JSONExampleResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) @@ -957,3 +1099,29 @@ func ParseURLEncodedExampleResponse(rsp *http.Response) (*URLEncodedExampleRespo return response, nil } + +// ParseHeadersExampleResponse parses an HTTP response from a HeadersExampleWithResponse call +func ParseHeadersExampleResponse(rsp *http.Response) (*HeadersExampleResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &HeadersExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go index 488bcd2700..aa801b00cd 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/examples/strict-server/echo/server.gen.go @@ -13,6 +13,7 @@ import ( "io" "io/ioutil" "mime/multipart" + "net/http" "net/url" "path" "strings" @@ -36,6 +37,12 @@ type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody Example @@ -60,6 +67,9 @@ type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. type URLEncodedExampleFormdataRequestBody Example +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody Example + // ServerInterface represents all server handlers. type ServerInterface interface { @@ -80,6 +90,9 @@ type ServerInterface interface { // (POST /urlencoded) URLEncodedExample(ctx echo.Context) error + + // (POST /with-headers) + HeadersExample(ctx echo.Context, params HeadersExampleParams) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -141,6 +154,52 @@ func (w *ServerInterfaceWrapper) URLEncodedExample(ctx echo.Context) error { return err } +// HeadersExample converts echo context to params. +func (w *ServerInterfaceWrapper) HeadersExample(ctx echo.Context) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := ctx.Request().Header + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header1, got %d", n)) + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header1: %s", err)) + } + + params.Header1 = Header1 + } else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter header1 is required, but not found")) + } + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header2, got %d", n)) + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header2: %s", err)) + } + + params.Header2 = &Header2 + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.HeadersExample(ctx, params) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -175,6 +234,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/text", wrapper.TextExample) router.POST(baseURL+"/unknown", wrapper.UnknownExample) router.POST(baseURL+"/urlencoded", wrapper.URLEncodedExample) + router.POST(baseURL+"/with-headers", wrapper.HeadersExample) } @@ -190,10 +250,6 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { type JSONExample400TextResponse Badrequest -func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type JSONExampledefaultResponse struct { StatusCode int } @@ -202,17 +258,10 @@ type MultipartExampleRequestObject struct { Body *multipart.Reader } -type MultipartExample200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error type MultipartExample400TextResponse Badrequest -func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type MultipartExampledefaultResponse struct { StatusCode int } @@ -233,42 +282,23 @@ func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, e type MultipleRequestAndResponseTypes200FormdataResponse Example -func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - type MultipleRequestAndResponseTypes200ImagepngResponse struct { Body io.Reader ContentLength int64 } -type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error type MultipleRequestAndResponseTypes200TextResponse string -func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } type TextExample200TextResponse string -func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExample400TextResponse Badrequest -func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type TextExampledefaultResponse struct { StatusCode int } @@ -284,10 +314,6 @@ type UnknownExample200Videomp4Response struct { type UnknownExample400TextResponse Badrequest -func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type UnknownExampledefaultResponse struct { StatusCode int } @@ -298,17 +324,34 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) +type URLEncodedExample400TextResponse Badrequest + +type URLEncodedExampledefaultResponse struct { + StatusCode int } -type URLEncodedExample400TextResponse Badrequest +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} -func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int } -type URLEncodedExampledefaultResponse struct { +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400TextResponse Badrequest + +type HeadersExampledefaultResponse struct { StatusCode int } @@ -332,6 +375,9 @@ type StrictServerInterface interface { // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} } type StrictHandlerFunc func(ctx echo.Context, args interface{}) interface{} @@ -402,14 +448,13 @@ func (sh *strictHandler) MultipartExample(ctx echo.Context) error { response := handler(ctx, request) switch v := response.(type) { - case MultipartExample200MultipartformDataResponse: - if v.ContentLength != 0 { - ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + case MultipartExample200MultipartResponse: + writer := multipart.NewWriter(ctx.Response()) + ctx.Response().Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + return err } - return ctx.Stream(200, "multipart/form-data", v.Body) case MultipartExample400TextResponse: return ctx.Blob(400, "text/plain", []byte(v)) case MultipartExampledefaultResponse: @@ -490,14 +535,13 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error defer closer.Close() } return ctx.Stream(200, "image/png", v.Body) - case MultipleRequestAndResponseTypes200MultipartformDataResponse: - if v.ContentLength != 0 { - ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) - } - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + case MultipleRequestAndResponseTypes200MultipartResponse: + writer := multipart.NewWriter(ctx.Response()) + ctx.Response().Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + return err } - return ctx.Stream(200, "multipart/form-data", v.Body) case MultipleRequestAndResponseTypes200TextResponse: return ctx.Blob(200, "text/plain", []byte(v)) case error: @@ -625,20 +669,61 @@ func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { return nil } +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleParams) error { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.HeadersExample(ctx.Request().Context(), request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case HeadersExample200JSONResponse: + ctx.Response().Header().Set("header1", fmt.Sprint(v.Headers.Header1)) + ctx.Response().Header().Set("header2", fmt.Sprint(v.Headers.Header2)) + return ctx.JSON(200, v) + case HeadersExample400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case HeadersExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", - "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", - "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", - "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", - "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", - "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", - "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", - "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", - "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", - "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", - "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", + "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", + "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", + "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", + "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", + "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", + "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", + "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", + "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", + "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", + "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", + "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", + "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", + "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go index 9d98091174..13b1b57aea 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/examples/strict-server/gin/server.gen.go @@ -13,6 +13,7 @@ import ( "io" "io/ioutil" "mime/multipart" + "net/http" "net/url" "path" "strings" @@ -36,6 +37,12 @@ type MultipleRequestAndResponseTypesTextBody string // TextExampleTextBody defines parameters for TextExample. type TextExampleTextBody string +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody Example @@ -60,6 +67,9 @@ type TextExampleTextRequestBody TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. type URLEncodedExampleFormdataRequestBody Example +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody Example + // ServerInterface represents all server handlers. type ServerInterface interface { @@ -80,6 +90,9 @@ type ServerInterface interface { // (POST /urlencoded) URLEncodedExample(c *gin.Context) + + // (POST /with-headers) + HeadersExample(c *gin.Context, params HeadersExampleParams) } // ServerInterfaceWrapper converts contexts to parameters. @@ -150,6 +163,64 @@ func (siw *ServerInterfaceWrapper) URLEncodedExample(c *gin.Context) { siw.Handler.URLEncodedExample(c) } +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := c.Request.Header + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for header1, got %d", n)}) + return + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter header1: %s", err)}) + return + } + + params.Header1 = Header1 + + } else { + c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Header parameter header1 is required, but not found: %s", err)}) + return + } + + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for header2, got %d", n)}) + return + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter header2: %s", err)}) + return + } + + params.Header2 = &Header2 + + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.HeadersExample(c, params) +} + // GinServerOptions provides options for the Gin server. type GinServerOptions struct { BaseURL string @@ -180,6 +251,8 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options router.POST(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) + router.POST(options.BaseURL+"/with-headers", wrapper.HeadersExample) + return router } @@ -195,10 +268,6 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { type JSONExample400TextResponse Badrequest -func (t JSONExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type JSONExampledefaultResponse struct { StatusCode int } @@ -207,17 +276,10 @@ type MultipartExampleRequestObject struct { Body *multipart.Reader } -type MultipartExample200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error type MultipartExample400TextResponse Badrequest -func (t MultipartExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type MultipartExampledefaultResponse struct { StatusCode int } @@ -238,42 +300,23 @@ func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, e type MultipleRequestAndResponseTypes200FormdataResponse Example -func (t MultipleRequestAndResponseTypes200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) -} - type MultipleRequestAndResponseTypes200ImagepngResponse struct { Body io.Reader ContentLength int64 } -type MultipleRequestAndResponseTypes200MultipartformDataResponse struct { - Body io.Reader - ContentLength int64 -} +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error type MultipleRequestAndResponseTypes200TextResponse string -func (t MultipleRequestAndResponseTypes200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } type TextExample200TextResponse string -func (t TextExample200TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((string)(t)) -} - type TextExample400TextResponse Badrequest -func (t TextExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type TextExampledefaultResponse struct { StatusCode int } @@ -289,10 +332,6 @@ type UnknownExample200Videomp4Response struct { type UnknownExample400TextResponse Badrequest -func (t UnknownExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) -} - type UnknownExampledefaultResponse struct { StatusCode int } @@ -303,17 +342,34 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -func (t URLEncodedExample200FormdataResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Example)(t)) +type URLEncodedExample400TextResponse Badrequest + +type URLEncodedExampledefaultResponse struct { + StatusCode int } -type URLEncodedExample400TextResponse Badrequest +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} -func (t URLEncodedExample400TextResponse) MarshalJSON() ([]byte, error) { - return json.Marshal((Badrequest)(t)) +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int } -type URLEncodedExampledefaultResponse struct { +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400TextResponse Badrequest + +type HeadersExampledefaultResponse struct { StatusCode int } @@ -337,6 +393,9 @@ type StrictServerInterface interface { // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} } type StrictHandlerFunc func(ctx *gin.Context, args interface{}) interface{} @@ -408,11 +467,13 @@ func (sh *strictHandler) MultipartExample(ctx *gin.Context) { response := handler(ctx, request) switch v := response.(type) { - case MultipartExample200MultipartformDataResponse: - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + case MultipartExample200MultipartResponse: + writer := multipart.NewWriter(ctx.Writer) + ctx.Writer.Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + ctx.Error(err) } - ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) case MultipartExample400TextResponse: ctx.Data(400, "text/plain", []byte(v)) case MultipartExampledefaultResponse: @@ -493,11 +554,13 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { defer closer.Close() } ctx.DataFromReader(200, v.ContentLength, "image/png", v.Body, nil) - case MultipleRequestAndResponseTypes200MultipartformDataResponse: - if closer, ok := v.Body.(io.ReadCloser); ok { - defer closer.Close() + case MultipleRequestAndResponseTypes200MultipartResponse: + writer := multipart.NewWriter(ctx.Writer) + ctx.Writer.Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + ctx.Error(err) } - ctx.DataFromReader(200, v.ContentLength, "multipart/form-data", v.Body, nil) case MultipleRequestAndResponseTypes200TextResponse: ctx.Data(200, "text/plain", []byte(v)) case error: @@ -620,20 +683,61 @@ func (sh *strictHandler) URLEncodedExample(ctx *gin.Context) { } } +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleParams) { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.HeadersExample(ctx, request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case HeadersExample200JSONResponse: + ctx.Header("header1", fmt.Sprint(v.Headers.Header1)) + ctx.Header("header2", fmt.Sprint(v.Headers.Header2)) + ctx.JSON(200, v) + case HeadersExample400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case HeadersExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xWy27bOhD9FWLuXSqW22alXVNk0WeAJF0VXdDi2GJKcVhyZNkI9O8FJdmNXBlwUife", - "dCc+5szhmYfmHnIqHVm0HCC7B4/BkQ3YLmZSefxZYeC4ysky2vaTccWpM1LbuAp5gaWMX/97nEMG/6W/", - "QdPuNKQPwJqmSUBhyL12rMlCBhdSXW9Pkx5yhASvHUIGgb22C2gSwJUsncF45jw59Kw78ktpKhwxaZLN", - "Ds3uMO/ZaDuneHnI6h1ZltoGofR8jh4ti14FETGCCJVz5BmVmK1F9JCzCOiX6CEB1hyJwc3DfdETDpDA", - "En3oHL2aTCfT+BxyaKXTkMGbdisBJ7loH5TeBWr1dtRpMeT64ebqi9BByIqplKxzacxalNKHQhqDSmjL", - "FDlWOYcJtK68jMbvVW9+2WuZQK/4Ban1Tuilc0bnrd2W0GEJsIlU0wo+SLTX0+lzuNlNsquPUeLzztkY", - "xpbUIFsjzFxWZkT0r/aHpdoK9J58/7K0rAxrJz0/DNZQ7c+bK4dIvsVL5+TLMyVZPpPqx/J0UuH7ZjBa", - "JDcF1UEUVAsmoVAaUWsuxMZwp7q1FVIEbRcGxYZUMhpJg333emvVdf+W24jx7LWUDFBWZ3Vdn7XBq7xB", - "m5NC9TRYXcoFps4uhuYRWzJkMFtzTNs/u+uRkijZ+5fZdflC7eSf0uOF3dVehNjf725xdVCrO2LI/+pN", - "L9Csqm5zv2a91SGyPTGDDlBxqRVSWrrzRyKfStRBKe7R9frTZXfnsfPO0Wr+kR3reH5PEZY4zrejb4Ds", - "2z1U3kAGBbPL0rQbmSehlosF+ommNA6/zffmVwAAAP//pen/+5gMAAA=", + "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", + "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", + "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", + "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", + "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", + "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", + "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", + "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", + "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", + "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", + "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", + "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", + "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/strict-schema.yaml b/examples/strict-server/strict-schema.yaml index 5822c3bb37..6ca528dd02 100644 --- a/examples/strict-server/strict-schema.yaml +++ b/examples/strict-server/strict-schema.yaml @@ -146,6 +146,44 @@ paths: schema: type: string format: byte + /with-headers: + post: + operationId: Headers Example + description: Headers can be received and returned via structs + parameters: + - name: header1 + in: header + required: true + schema: + type: string + - name: header2 + in: header + required: false + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + description: OK + headers: + header1: + schema: + type: string + header2: + schema: + type: integer + content: + application/json: + schema: + $ref: "#/components/schemas/example" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error components: responses: badrequest: diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 9a2edbc8ea..30e18e3bfb 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -691,12 +691,14 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response for _, contentType := range SortedContentKeys(response.Content) { content := response.Content[contentType] var tag string - switch contentType { - case "application/json": + switch { + case contentType == "application/json": tag = "JSON" - case "application/x-www-form-urlencoded": + case contentType == "application/x-www-form-urlencoded": tag = "Formdata" - case "text/plain": + case strings.HasPrefix(contentType, "multipart/"): + tag = "Multipart" + case contentType == "text/plain": tag = "Text" default: rcd := ResponseContentDefinition{ diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl index 7b6e2ce8fe..ba2da838b1 100644 --- a/pkg/codegen/templates/strict/strict-chi.tmpl +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -91,7 +91,10 @@ type strictHandler struct { {{range $headers -}} w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) {{end -}} - w.Header().Set("Content-Type", {{if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}) + {{if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(w) + {{end -}} + w.Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}writer.FormDataContentType(){{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}v.ContentType{{end}}) {{if not .IsSupported -}} if v.ContentLength != 0 { w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) @@ -108,6 +111,11 @@ type strictHandler struct { } else { writeRaw(w, []byte(form.Encode())) } + {{else if eq .NameTag "Multipart" -}} + defer writer.Close() + if err := v(writer); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } {{else -}} if closer, ok := v.Body.(io.ReadCloser); ok { defer closer.Close() diff --git a/pkg/codegen/templates/strict/strict-echo.tmpl b/pkg/codegen/templates/strict/strict-echo.tmpl index 3db5ea22a9..245f3e59ba 100644 --- a/pkg/codegen/templates/strict/strict-echo.tmpl +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -97,6 +97,13 @@ type strictHandler struct { } else { return ctx.Blob({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(form.Encode())) } + {{else if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(ctx.Response()) + ctx.Response().Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + return err + } {{else -}} if v.ContentLength != 0 { ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) diff --git a/pkg/codegen/templates/strict/strict-gin.tmpl b/pkg/codegen/templates/strict/strict-gin.tmpl index 5a1ebf585b..f1f7e895c7 100644 --- a/pkg/codegen/templates/strict/strict-gin.tmpl +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -101,6 +101,13 @@ type strictHandler struct { } else { ctx.Data({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}, "{{.ContentType}}", []byte(form.Encode())) } + {{else if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(ctx.Writer) + ctx.Writer.Header().Set("Content-Type", writer.FormDataContentType()) + defer writer.Close() + if err := v(writer); err != nil { + ctx.Error(err) + } {{else -}} if closer, ok := v.Body.(io.ReadCloser); ok { defer closer.Close() diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 463299024c..90d861c2e0 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -23,24 +23,24 @@ {{if $hasHeaders}} type {{$opid}}{{$statusCode}}ResponseHeaders struct { - {{range .Headers}} + {{range .Headers -}} {{.GoName}} {{.Schema.TypeDecl}} - {{end}} + {{end -}} } {{end}} {{range .Contents}} {{if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if .IsSupported}}{{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} - {{if not (and (opts.AliasTypes) (.Schema.IsRef))}} + {{if and (not (and (opts.AliasTypes) (.Schema.IsRef))) (eq .NameTag "JSON")}} func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { return json.Marshal(({{.Schema.GoType}})(t)) } {{end}} {{else -}} type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response struct { - Body {{if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} Headers {{$opid}}{{$statusCode}}ResponseHeaders {{end -}} @@ -57,7 +57,7 @@ ContentLength int64 {{end -}} } - {{if .IsSupported}} + {{if eq .NameTag "JSON"}} func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go index 98a648b0e4..af4f912042 100644 --- a/pkg/runtime/bindform.go +++ b/pkg/runtime/bindform.go @@ -30,9 +30,9 @@ func BindForm(ptr interface{}, form map[string][]string, files map[string][]*mul tValue := ptrVal.Type() for i := 0; i < tValue.NumField(); i++ { - field := tValue.Field(i) - tag := field.Tag.Get(tagName) - if !field.IsExported() || tag == "-" { + field := ptrVal.Field(i) + tag := tValue.Field(i).Tag.Get(tagName) + if !field.CanInterface() || tag == "-" { continue } tag = strings.Split(tag, ",")[0] // extract the name of the tag @@ -55,13 +55,13 @@ func BindForm(ptr interface{}, form map[string][]string, files map[string][]*mul if encoding.Explode != nil { explode = *encoding.Explode } - if err := BindStyledParameterWithLocation(encoding.Style, explode, tag, ParamLocationUndefined, value, ptrVal.Field(i).Addr().Interface()); err != nil { + if err := BindStyledParameterWithLocation(encoding.Style, explode, tag, ParamLocationUndefined, value, field.Addr().Interface()); err != nil { return err } } } else { // regular form data - if _, err := bindFormImpl(ptrVal.Field(i), form, files, tag); err != nil { + if _, err := bindFormImpl(field, form, files, tag); err != nil { return err } } @@ -78,19 +78,19 @@ func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url tValue := ptrVal.Type() result := make(url.Values) for i := 0; i < tValue.NumField(); i++ { - field := tValue.Field(i) - tag := field.Tag.Get(tagName) - if !field.IsExported() || tag == "-" { + field := ptrVal.Field(i) + tag := tValue.Field(i).Tag.Get(tagName) + if !field.CanInterface() || tag == "-" { continue } omitEmpty := strings.HasSuffix(tag, ",omitempty") - if omitEmpty && ptrVal.Field(i).IsZero() { + if omitEmpty && field.IsZero() { continue } tag = strings.Split(tag, ",")[0] // extract the name of the tag if encoding, ok := encodings[tag]; ok && encoding.ContentType != "" { if strings.HasPrefix(encoding.ContentType, jsonContentType) { - if data, err := json.Marshal(ptrVal.Field(i)); err != nil { + if data, err := json.Marshal(field); err != nil { return nil, err } else { result[tag] = append(result[tag], string(data)) @@ -98,7 +98,7 @@ func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url } return nil, errors.New("unsupported encoding, only application/json is supported") } else { - marshalFormImpl(ptrVal.Field(i), result, tag) + marshalFormImpl(field, result, tag) } } return result, nil @@ -165,7 +165,7 @@ func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][] } hasData = hasData || additionalPropertiesHasData } - if !field.IsExported() || tag == "-" { + if !v.Field(i).CanInterface() || tag == "-" { continue } tag = strings.Split(tag, ",")[0] // extract the name of the tag @@ -238,7 +238,7 @@ func marshalFormImpl(v reflect.Value, result url.Values, name string) { } continue } - if !field.IsExported() || tag == "-" { + if !v.Field(i).CanInterface() || tag == "-" { continue } tag = strings.Split(tag, ",")[0] // extract the name of the tag From 15d7246bc2a660565b7d9542d0e3493b513b9b2a Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Mon, 2 May 2022 14:21:42 +0300 Subject: [PATCH 12/19] Added sorting of headers in response objects --- pkg/codegen/operations.go | 3 ++- pkg/codegen/utils.go | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 30e18e3bfb..bb80d307f8 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -733,7 +733,8 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response } var responseHeaderDefinitions []ResponseHeaderDefinition - for headerName, header := range response.Headers { + for _, headerName := range SortedHeadersKeys(response.Headers) { + header := response.Headers[headerName] contentSchema, err := GenerateGoSchema(header.Value.Schema, []string{}) if err != nil { return nil, fmt.Errorf("error generating response header definition: %w", err) diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index a7dc3cf710..0c8923a88e 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -136,6 +136,17 @@ func SortedResponsesKeys(dict openapi3.Responses) []string { return keys } +func SortedHeadersKeys(dict openapi3.Headers) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + // This returns Content dictionary keys in sorted order func SortedContentKeys(dict openapi3.Content) []string { keys := make([]string, len(dict)) From e7abd03b4b395cfbc79ac2d5f57cecc9ea48d89a Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Mon, 2 May 2022 17:13:19 +0300 Subject: [PATCH 13/19] Added proper testing to strict servers --- examples/strict-server/chi/server.gen.go | 116 +++++++++-- examples/strict-server/chi/server.go | 94 +++++++++ examples/strict-server/client/client.gen.go | 93 +++++++++ examples/strict-server/echo/server.gen.go | 103 ++++++++-- examples/strict-server/echo/server.go | 94 +++++++++ examples/strict-server/gin/server.gen.go | 101 ++++++++-- examples/strict-server/gin/server.go | 94 +++++++++ examples/strict-server/strict-schema.yaml | 28 ++- examples/strict-server/strict_test.go | 204 ++++++++++++++++++++ 9 files changed, 886 insertions(+), 41 deletions(-) create mode 100644 examples/strict-server/strict_test.go diff --git a/examples/strict-server/chi/server.gen.go b/examples/strict-server/chi/server.gen.go index 69d738e971..002992920f 100644 --- a/examples/strict-server/chi/server.gen.go +++ b/examples/strict-server/chi/server.gen.go @@ -88,6 +88,9 @@ type ServerInterface interface { // (POST /unknown) UnknownExample(w http.ResponseWriter, r *http.Request) + // (POST /unspecified-content-type) + UnspecifiedContentType(w http.ResponseWriter, r *http.Request) + // (POST /urlencoded) URLEncodedExample(w http.ResponseWriter, r *http.Request) @@ -179,6 +182,21 @@ func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http handler(w, r.WithContext(ctx)) } +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UnspecifiedContentType(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + // URLEncodedExample operation middleware func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -386,6 +404,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/unknown", wrapper.UnknownExample) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) }) @@ -449,6 +470,8 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string +type MultipleRequestAndResponseTypes400TextResponse Badrequest + type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -476,6 +499,23 @@ type UnknownExampledefaultResponse struct { StatusCode int } +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400TextResponse Badrequest + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } @@ -531,6 +571,9 @@ type StrictServerInterface interface { // (POST /unknown) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} + // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} @@ -722,6 +765,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) writeRaw(w, ([]byte)(v)) + case MultipleRequestAndResponseTypes400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) case error: http.Error(w, v.Error(), http.StatusInternalServerError) case nil: @@ -810,6 +857,48 @@ func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) } } +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = r.Header.Get("Content-Type") + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.UnspecifiedContentType(ctx, request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case UnspecifiedContentType200VideoResponse: + w.Header().Set("Content-Type", v.ContentType) + if v.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + w.WriteHeader(200) + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, _ = io.Copy(w, v.Body) + case UnspecifiedContentType400TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + writeRaw(w, ([]byte)(v)) + case UnspecifiedContentTypedefaultResponse: + w.WriteHeader(v.StatusCode) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + // URLEncodedExample operation middleware func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Request) { var request URLEncodedExampleRequestObject @@ -915,19 +1004,20 @@ func writeRaw(w http.ResponseWriter, b []byte) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", - "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", - "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", - "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", - "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", - "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", - "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", - "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", - "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", - "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", - "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", - "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", - "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", + "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", + "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", + "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", + "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", + "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", + "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", + "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", + "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", + "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", + "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", + "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", + "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", + "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", + "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/chi/server.go b/examples/strict-server/chi/server.go index 8ce159176f..6ee53ea430 100644 --- a/examples/strict-server/chi/server.go +++ b/examples/strict-server/chi/server.go @@ -1,3 +1,97 @@ //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,spec,strict-server -o server.gen.go ../strict-schema.yaml package api + +import ( + "context" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} { + return JSONExample200JSONResponse(*request.Body) +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body} + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody) + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody) + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody) + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) + default: + return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} { + return TextExample200TextResponse(*request.Body) +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} { + return UnknownExample200Videomp4Response{Body: request.Body} +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType} +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} { + return URLEncodedExample200FormdataResponse(*request.Body) +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { + return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} +} diff --git a/examples/strict-server/client/client.gen.go b/examples/strict-server/client/client.gen.go index 87353cafbc..178ffd04b0 100644 --- a/examples/strict-server/client/client.gen.go +++ b/examples/strict-server/client/client.gen.go @@ -162,6 +162,9 @@ type ClientInterface interface { // UnknownExample request with any body UnknownExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // UnspecifiedContentType request with any body + UnspecifiedContentTypeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // URLEncodedExample request with any body URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -293,6 +296,18 @@ func (c *Client) UnknownExampleWithBody(ctx context.Context, contentType string, return c.Client.Do(req) } +func (c *Client) UnspecifiedContentTypeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnspecifiedContentTypeRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) URLEncodedExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewURLEncodedExampleRequestWithBody(c.Server, contentType, body) if err != nil { @@ -533,6 +548,35 @@ func NewUnknownExampleRequestWithBody(server string, contentType string, body io return req, nil } +// NewUnspecifiedContentTypeRequestWithBody generates requests for UnspecifiedContentType with any type of body +func NewUnspecifiedContentTypeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/unspecified-content-type") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewURLEncodedExampleRequestWithFormdataBody calls the generic URLEncodedExample builder with application/x-www-form-urlencoded body func NewURLEncodedExampleRequestWithFormdataBody(server string, body URLEncodedExampleFormdataRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -701,6 +745,9 @@ type ClientWithResponsesInterface interface { // UnknownExample request with any body UnknownExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnknownExampleResponse, error) + // UnspecifiedContentType request with any body + UnspecifiedContentTypeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnspecifiedContentTypeResponse, error) + // URLEncodedExample request with any body URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) @@ -819,6 +866,27 @@ func (r UnknownExampleResponse) StatusCode() int { return 0 } +type UnspecifiedContentTypeResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UnspecifiedContentTypeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UnspecifiedContentTypeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type URLEncodedExampleResponse struct { Body []byte HTTPResponse *http.Response @@ -947,6 +1015,15 @@ func (c *ClientWithResponses) UnknownExampleWithBodyWithResponse(ctx context.Con return ParseUnknownExampleResponse(rsp) } +// UnspecifiedContentTypeWithBodyWithResponse request with arbitrary body returning *UnspecifiedContentTypeResponse +func (c *ClientWithResponses) UnspecifiedContentTypeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UnspecifiedContentTypeResponse, error) { + rsp, err := c.UnspecifiedContentTypeWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnspecifiedContentTypeResponse(rsp) +} + // URLEncodedExampleWithBodyWithResponse request with arbitrary body returning *URLEncodedExampleResponse func (c *ClientWithResponses) URLEncodedExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*URLEncodedExampleResponse, error) { rsp, err := c.URLEncodedExampleWithBody(ctx, contentType, body, reqEditors...) @@ -1084,6 +1161,22 @@ func ParseUnknownExampleResponse(rsp *http.Response) (*UnknownExampleResponse, e return response, nil } +// ParseUnspecifiedContentTypeResponse parses an HTTP response from a UnspecifiedContentTypeWithResponse call +func ParseUnspecifiedContentTypeResponse(rsp *http.Response) (*UnspecifiedContentTypeResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UnspecifiedContentTypeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseURLEncodedExampleResponse parses an HTTP response from a URLEncodedExampleWithResponse call func ParseURLEncodedExampleResponse(rsp *http.Response) (*URLEncodedExampleResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go index aa801b00cd..a33e4b4a6a 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/examples/strict-server/echo/server.gen.go @@ -88,6 +88,9 @@ type ServerInterface interface { // (POST /unknown) UnknownExample(ctx echo.Context) error + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx echo.Context) error + // (POST /urlencoded) URLEncodedExample(ctx echo.Context) error @@ -145,6 +148,15 @@ func (w *ServerInterfaceWrapper) UnknownExample(ctx echo.Context) error { return err } +// UnspecifiedContentType converts echo context to params. +func (w *ServerInterfaceWrapper) UnspecifiedContentType(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UnspecifiedContentType(ctx) + return err +} + // URLEncodedExample converts echo context to params. func (w *ServerInterfaceWrapper) URLEncodedExample(ctx echo.Context) error { var err error @@ -233,6 +245,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) router.POST(baseURL+"/text", wrapper.TextExample) router.POST(baseURL+"/unknown", wrapper.UnknownExample) + router.POST(baseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) router.POST(baseURL+"/urlencoded", wrapper.URLEncodedExample) router.POST(baseURL+"/with-headers", wrapper.HeadersExample) @@ -291,6 +304,8 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string +type MultipleRequestAndResponseTypes400TextResponse Badrequest + type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -318,6 +333,23 @@ type UnknownExampledefaultResponse struct { StatusCode int } +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400TextResponse Badrequest + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } @@ -373,6 +405,9 @@ type StrictServerInterface interface { // (POST /unknown) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} + // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} @@ -544,6 +579,8 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error } case MultipleRequestAndResponseTypes200TextResponse: return ctx.Blob(200, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) case error: return v case nil: @@ -626,6 +663,45 @@ func (sh *strictHandler) UnknownExample(ctx echo.Context) error { return nil } +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(ctx echo.Context) error { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = ctx.Request().Header.Get("Content-Type") + + request.Body = ctx.Request().Body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.UnspecifiedContentType(ctx.Request().Context(), request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case UnspecifiedContentType200VideoResponse: + if v.ContentLength != 0 { + ctx.Response().Header().Set("Content-Length", fmt.Sprint(v.ContentLength)) + } + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + return ctx.Stream(200, v.ContentType, v.Body) + case UnspecifiedContentType400TextResponse: + return ctx.Blob(400, "text/plain", []byte(v)) + case UnspecifiedContentTypedefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + // URLEncodedExample operation middleware func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { var request URLEncodedExampleRequestObject @@ -711,19 +787,20 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", - "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", - "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", - "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", - "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", - "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", - "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", - "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", - "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", - "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", - "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", - "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", - "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", + "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", + "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", + "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", + "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", + "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", + "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", + "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", + "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", + "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", + "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", + "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", + "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", + "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", + "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/echo/server.go b/examples/strict-server/echo/server.go index d824806492..1d36c427c2 100644 --- a/examples/strict-server/echo/server.go +++ b/examples/strict-server/echo/server.go @@ -1,3 +1,97 @@ //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,server,spec,strict-server -o server.gen.go ../strict-schema.yaml package api + +import ( + "context" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} { + return JSONExample200JSONResponse(*request.Body) +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body} + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody) + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody) + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody) + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) + default: + return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} { + return TextExample200TextResponse(*request.Body) +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} { + return UnknownExample200Videomp4Response{Body: request.Body} +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType} +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} { + return URLEncodedExample200FormdataResponse(*request.Body) +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { + return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} +} diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go index 13b1b57aea..68e0c9834c 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/examples/strict-server/gin/server.gen.go @@ -88,6 +88,9 @@ type ServerInterface interface { // (POST /unknown) UnknownExample(c *gin.Context) + // (POST /unspecified-content-type) + UnspecifiedContentType(c *gin.Context) + // (POST /urlencoded) URLEncodedExample(c *gin.Context) @@ -153,6 +156,16 @@ func (siw *ServerInterfaceWrapper) UnknownExample(c *gin.Context) { siw.Handler.UnknownExample(c) } +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.UnspecifiedContentType(c) +} + // URLEncodedExample operation middleware func (siw *ServerInterfaceWrapper) URLEncodedExample(c *gin.Context) { @@ -249,6 +262,8 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options router.POST(options.BaseURL+"/unknown", wrapper.UnknownExample) + router.POST(options.BaseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + router.POST(options.BaseURL+"/urlencoded", wrapper.URLEncodedExample) router.POST(options.BaseURL+"/with-headers", wrapper.HeadersExample) @@ -309,6 +324,8 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string +type MultipleRequestAndResponseTypes400TextResponse Badrequest + type TextExampleRequestObject struct { Body *TextExampleTextRequestBody } @@ -336,6 +353,23 @@ type UnknownExampledefaultResponse struct { StatusCode int } +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400TextResponse Badrequest + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + type URLEncodedExampleRequestObject struct { Body *URLEncodedExampleFormdataRequestBody } @@ -391,6 +425,9 @@ type StrictServerInterface interface { // (POST /unknown) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} + // (POST /urlencoded) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} @@ -563,6 +600,8 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { } case MultipleRequestAndResponseTypes200TextResponse: ctx.Data(200, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) case error: ctx.Error(v) case nil: @@ -640,6 +679,41 @@ func (sh *strictHandler) UnknownExample(ctx *gin.Context) { } } +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(ctx *gin.Context) { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = ctx.ContentType() + + request.Body = ctx.Request.Body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.UnspecifiedContentType(ctx, request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case UnspecifiedContentType200VideoResponse: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, v.ContentType, v.Body, nil) + case UnspecifiedContentType400TextResponse: + ctx.Data(400, "text/plain", []byte(v)) + case UnspecifiedContentTypedefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + // URLEncodedExample operation middleware func (sh *strictHandler) URLEncodedExample(ctx *gin.Context) { var request URLEncodedExampleRequestObject @@ -725,19 +799,20 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXTW/bOBD9K8TsHmnLyebk22YRYL8DJOmpyGEsjm2mEsmSI8tGoP9eUJIdy5EDJ7UT", - "oOhNpDhvHt98kHyE1ObOGjIcYPwInoKzJlA9mKDy9LWgwHGUWsNk6k+mJScuQ23iKKRzyjF+/eppCmP4", - "JXkCTZq/IdkCq6pKgqKQeu1YWwNjuER1s/krW8geErxyBGMI7LWZQSWBlpi7jOI/560jz7ohv8CsoB6T", - "Sq5n7OSB0paNNlMbF3dZ/WENozZBKD2dkifDolVBRIwgQuGc9UxKTFYiekhZBPIL8iCBNUdicLs9L1rC", - "ASQsyIfG0dlwNBzF7VhHBp2GMfxWT0lwyPN6Q8lDsLXezjZadLn+fXv9v9BBYME2R9YpZtlK5OjDHLOM", - "lNCGbeRYpByGULvyGI3/Uq35VaulhFbxS6tWO6FH5zKd1nYbQoclwDpSVS14J9HOR6NTuNlNsut/osQX", - "jbM+jA2pTrZGmCkWWY/on8wXY0sjyHvr250leZGxduh5O1hdtf9bLzlE8g1eMrU+HyhkPJHqx/L0ocK3", - "zaC3SG7ntgxibkvBVijCTJSa52JtuFPd2ggUQZtZRmJNSvZGMqO2e/1u1E27l7uIcfJakh2U5aAsy0Ed", - "vMJnZFKrSL0NVuc4o8SZWdc8YiPDGCYrjmn7vLseKYnk3lNm1+U7tZOfSvcXdlN7EWJ/v7uj5UGt7ogh", - "/649vUOzKprJ/Zq1VofI9sYMOkDFhVZkk9xdvBL5o0TtlOIeXW/+vWrWvPa+c7Saf2XHOp7fDwpLPGQH", - "c0JFPuw/nP9sFogUjZjEEzclvSAl0CjhiQtvSImFxvUl9tlZ3AI8hdWhx5y49vr5EWIrgIYGSDCY02Z8", - "1iaB9lFZ9gXJF3qGfBHrHHpstWGaUVTk/ge+XkvYivJa2Rfbr9yI1rfsSbXq5Hkan531E61JlsJnMaLM", - "bpwkzdNuGEqczcgPtU3iI626r74FAAD//1evVKNADwAA", + "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", + "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", + "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", + "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", + "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", + "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", + "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", + "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", + "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", + "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", + "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", + "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", + "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", + "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/gin/server.go b/examples/strict-server/gin/server.go index 1e0f5085a5..350a480047 100644 --- a/examples/strict-server/gin/server.go +++ b/examples/strict-server/gin/server.go @@ -1,3 +1,97 @@ //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,gin,spec,strict-server -o server.gen.go ../strict-schema.yaml package api + +import ( + "context" + "io" + "mime/multipart" +) + +type StrictServer struct { +} + +func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) interface{} { + return JSONExample200JSONResponse(*request.Body) +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) interface{} { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body} + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody) + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody) + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody) + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + w, err := writer.CreatePart(part.Header) + if err != nil { + return err + } + _, err = io.Copy(w, part) + if err != nil { + return err + } + if err = part.Close(); err != nil { + return err + } + } + }) + default: + return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} { + return TextExample200TextResponse(*request.Body) +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) interface{} { + return UnknownExample200Videomp4Response{Body: request.Body} +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) interface{} { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType} +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) interface{} { + return URLEncodedExample200FormdataResponse(*request.Body) +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { + return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} +} diff --git a/examples/strict-server/strict-schema.yaml b/examples/strict-server/strict-schema.yaml index 6ca528dd02..6b48f99118 100644 --- a/examples/strict-server/strict-schema.yaml +++ b/examples/strict-server/strict-schema.yaml @@ -106,7 +106,7 @@ paths: description: Unknown error /multiple: post: - operationId: Multiple request and response types + operationId: MultipleRequestAndResponseTypes description: Shows how to deal with multiple content types in a single request requestBody: content: @@ -146,9 +146,11 @@ paths: schema: type: string format: byte + 400: + $ref: "#/components/responses/badrequest" /with-headers: post: - operationId: Headers Example + operationId: HeadersExample description: Headers can be received and returned via structs parameters: - name: header1 @@ -184,6 +186,28 @@ paths: $ref: "#/components/responses/badrequest" default: description: Unknown error + /unspecified-content-type: + post: + operationId: UnspecifiedContentType + description: Concrete content type is not specified by the schema, so we must pass it to client code + requestBody: + content: + image/*: + schema: + type: string + format: byte + responses: + 200: + description: OK + content: + video/*: + schema: + type: string + format: byte + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error components: responses: badrequest: diff --git a/examples/strict-server/strict_test.go b/examples/strict-server/strict_test.go new file mode 100644 index 0000000000..e7766197d4 --- /dev/null +++ b/examples/strict-server/strict_test.go @@ -0,0 +1,204 @@ +package strict_server + +import ( + "bytes" + "encoding/json" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-chi/chi/v5" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + + chiapi "github.com/deepmap/oapi-codegen/examples/strict-server/chi" + client "github.com/deepmap/oapi-codegen/examples/strict-server/client" + echoapi "github.com/deepmap/oapi-codegen/examples/strict-server/echo" + ginapi "github.com/deepmap/oapi-codegen/examples/strict-server/gin" + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/deepmap/oapi-codegen/pkg/testutil" +) + +func TestChiServer(t *testing.T) { + server := chiapi.StrictServer{} + strictHandler := chiapi.NewStrictHandler(server, nil) + r := chi.NewRouter() + handler := chiapi.HandlerFromMux(strictHandler, r) + testImpl(t, handler) +} + +func TestEchoServer(t *testing.T) { + server := echoapi.StrictServer{} + strictHandler := echoapi.NewStrictHandler(server, nil) + e := echo.New() + echoapi.RegisterHandlers(e, strictHandler) + testImpl(t, e) +} + +func TestGinServer(t *testing.T) { + server := ginapi.StrictServer{} + strictHandler := ginapi.NewStrictHandler(server, nil) + gin.SetMode(gin.ReleaseMode) + r := gin.New() + handler := ginapi.RegisterHandlers(r, strictHandler) + testImpl(t, handler) +} + +func testImpl(t *testing.T, handler http.Handler) { + t.Run("JSONExample", func(t *testing.T) { + value := "123" + requestBody := client.Example{Value: &value} + rr := testutil.NewRequest().Post("/json").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody client.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("URLEncodedExample", func(t *testing.T) { + value := "456" + requestBody := client.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody client.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipartExample", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multipart").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("TextExample", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/text").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("UnknownExample", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/unknown").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "video/mp4", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { + value := "123" + requestBody := client.Example{Value: &value} + rr := testutil.NewRequest().Post("/multiple").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody client.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesFormdata", func(t *testing.T) { + value := "456" + requestBody := client.Example{Value: &value} + requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) + assert.NoError(t, err) + rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) + values, err := url.ParseQuery(rr.Body.String()) + assert.NoError(t, err) + var responseBody client.Example + err = runtime.BindForm(&responseBody, values, nil, nil) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) + t.Run("MultipleRequestAndResponseTypesMultipart", func(t *testing.T) { + value := "789" + fieldName := "value" + var writer bytes.Buffer + mw := multipart.NewWriter(&writer) + field, err := mw.CreateFormField(fieldName) + assert.NoError(t, err) + _, _ = field.Write([]byte(value)) + assert.NoError(t, mw.Close()) + rr := testutil.NewRequest().Post("/multiple").WithContentType(mw.FormDataContentType()).WithBody(writer.Bytes()).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + contentType, params, err := mime.ParseMediaType(rr.Header().Get("Content-Type")) + assert.NoError(t, err) + assert.Equal(t, "multipart/form-data", contentType) + reader := multipart.NewReader(rr.Body, params["boundary"]) + part, err := reader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part.FormName(), fieldName) + readValue, err := io.ReadAll(part) + assert.NoError(t, err) + assert.Equal(t, value, string(readValue)) + _, err = reader.NextPart() + assert.Equal(t, io.EOF, err) + }) + t.Run("MultipleRequestAndResponseTypesText", func(t *testing.T) { + value := "text" + rr := testutil.NewRequest().Post("/multiple").WithContentType("text/plain").WithBody([]byte(value)).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "text/plain", rr.Header().Get("Content-Type")) + assert.Equal(t, value, rr.Body.String()) + }) + t.Run("MultipleRequestAndResponseTypesImage", func(t *testing.T) { + data := []byte("unknown data") + rr := testutil.NewRequest().Post("/multiple").WithContentType("image/png").WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "image/png", rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) + t.Run("HeadersExample", func(t *testing.T) { + header1 := "value1" + header2 := "890" + value := "asdf" + requestBody := client.Example{Value: &value} + rr := testutil.NewRequest().Post("/with-headers").WithHeader("header1", header1).WithHeader("header2", header2).WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody client.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + assert.Equal(t, header1, rr.Header().Get("header1")) + assert.Equal(t, header2, rr.Header().Get("header2")) + }) + t.Run("UnspecifiedContentType", func(t *testing.T) { + data := []byte("image data") + contentType := "image/jpeg" + rr := testutil.NewRequest().Post("/unspecified-content-type").WithContentType(contentType).WithBody(data).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, contentType, rr.Header().Get("Content-Type")) + assert.Equal(t, data, rr.Body.Bytes()) + }) +} From 240fa5c7cf9e883fcc39546510fc3a03652fde67 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 22 May 2022 19:06:22 +0300 Subject: [PATCH 14/19] Fix after master merge --- cmd/oapi-codegen/oapi-codegen.go | 2 + .../authenticated-api/echo/api/api.gen.go | 2 +- .../petstore-expanded/chi/api/petstore.gen.go | 2 +- .../echo/api/petstore-types.gen.go | 2 +- .../gin/api/petstore-types.gen.go | 5 +- .../petstore-expanded/petstore-client.gen.go | 2 +- ...petstore.gen.go => petstore-server.gen.go} | 43 +- .../strict/api/petstore-types.gen.go | 46 ++ .../petstore-expanded/strict/api/petstore.go | 7 +- .../strict/api/server.cfg.yaml | 6 + .../strict/api/types.cfg.yaml | 4 + .../petstore-expanded/strict/petstore_test.go | 8 +- examples/strict-server/chi/server.cfg.yaml | 6 + examples/strict-server/chi/server.gen.go | 63 +- examples/strict-server/chi/server.go | 3 +- examples/strict-server/chi/types.cfg.yaml | 4 + examples/strict-server/chi/types.gen.go | 51 ++ examples/strict-server/client/client.cfg.yaml | 5 + examples/strict-server/client/client.gen.go | 24 +- examples/strict-server/client/client.go | 2 +- examples/strict-server/echo/server.cfg.yaml | 6 + examples/strict-server/echo/server.gen.go | 63 +- examples/strict-server/echo/server.go | 3 +- examples/strict-server/echo/types.cfg.yaml | 4 + examples/strict-server/gin/server.cfg.yaml | 6 + examples/strict-server/gin/server.gen.go | 63 +- examples/strict-server/gin/server.go | 3 +- examples/strict-server/gin/types.cfg.yaml | 4 + internal/test/client/client.gen.go | 4 +- internal/test/components/components.gen.go | 777 +----------------- internal/test/issues/issue-312/issue.gen.go | 2 +- internal/test/schemas/schemas.gen.go | 2 +- internal/test/server/server.gen.go | 4 +- pkg/codegen/codegen.go | 4 +- pkg/codegen/configuration.go | 1 + pkg/codegen/operations.go | 8 +- .../templates/strict/strict-interface.tmpl | 4 +- 37 files changed, 218 insertions(+), 1027 deletions(-) rename examples/petstore-expanded/strict/api/{petstore.gen.go => petstore-server.gen.go} (94%) create mode 100644 examples/petstore-expanded/strict/api/petstore-types.gen.go create mode 100644 examples/petstore-expanded/strict/api/server.cfg.yaml create mode 100644 examples/petstore-expanded/strict/api/types.cfg.yaml create mode 100644 examples/strict-server/chi/server.cfg.yaml create mode 100644 examples/strict-server/chi/types.cfg.yaml create mode 100644 examples/strict-server/chi/types.gen.go create mode 100644 examples/strict-server/client/client.cfg.yaml create mode 100644 examples/strict-server/echo/server.cfg.yaml create mode 100644 examples/strict-server/echo/types.cfg.yaml create mode 100644 examples/strict-server/gin/server.cfg.yaml create mode 100644 examples/strict-server/gin/types.cfg.yaml diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index fe43be6a84..1e504a1d3d 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -321,6 +321,8 @@ func newConfigFromOldConfig(c oldConfiguration) configuration { opts.Generate.EchoServer = true case "gin": opts.Generate.GinServer = true + case "strict-server": + opts.Generate.Strict = true case "types": opts.Generate.Models = true case "spec": diff --git a/examples/authenticated-api/echo/api/api.gen.go b/examples/authenticated-api/echo/api/api.gen.go index ad130ab2b9..14f393d19d 100644 --- a/examples/authenticated-api/echo/api/api.gen.go +++ b/examples/authenticated-api/echo/api/api.gen.go @@ -46,7 +46,7 @@ type ThingWithID struct { } // AddThingJSONRequestBody defines body for AddThing for application/json ContentType. -type AddThingJSONRequestBody Thing +type AddThingJSONRequestBody = Thing // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go index f9c1e73837..e90313f594 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/api/petstore.gen.go @@ -58,7 +58,7 @@ type FindPetsParams struct { } // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody NewPet +type AddPetJSONRequestBody = NewPet // ServerInterface represents all server handlers. type ServerInterface interface { diff --git a/examples/petstore-expanded/echo/api/petstore-types.gen.go b/examples/petstore-expanded/echo/api/petstore-types.gen.go index 2e77fc7d96..e3c73d101f 100644 --- a/examples/petstore-expanded/echo/api/petstore-types.gen.go +++ b/examples/petstore-expanded/echo/api/petstore-types.gen.go @@ -43,4 +43,4 @@ type FindPetsParams struct { } // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody NewPet +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/gin/api/petstore-types.gen.go b/examples/petstore-expanded/gin/api/petstore-types.gen.go index 67ee510b52..e3c73d101f 100644 --- a/examples/petstore-expanded/gin/api/petstore-types.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-types.gen.go @@ -42,8 +42,5 @@ type FindPetsParams struct { Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody = NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody = AddPetJSONBody +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go index eaad4d8185..bbd8aa2e60 100644 --- a/examples/petstore-expanded/petstore-client.gen.go +++ b/examples/petstore-expanded/petstore-client.gen.go @@ -57,7 +57,7 @@ type FindPetsParams struct { } // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody NewPet +type AddPetJSONRequestBody = NewPet // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/petstore-expanded/strict/api/petstore.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go similarity index 94% rename from examples/petstore-expanded/strict/api/petstore.gen.go rename to examples/petstore-expanded/strict/api/petstore-server.gen.go index 263e7f5486..016667a730 100644 --- a/examples/petstore-expanded/strict/api/petstore.gen.go +++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go @@ -20,45 +20,6 @@ import ( "github.com/go-chi/chi/v5" ) -// Error defines model for Error. -type Error struct { - // Error code - Code int32 `json:"code"` - - // Error message - Message string `json:"message"` -} - -// NewPet defines model for NewPet. -type NewPet struct { - // Name of the pet - Name string `json:"name"` - - // Type of the pet - Tag *string `json:"tag,omitempty"` -} - -// 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"` -} - -// FindPetsParams defines parameters for FindPets. -type FindPetsParams struct { - // tags to filter by - Tags *[]string `json:"tags,omitempty"` - - // maximum number of results to return - Limit *int32 `json:"limit,omitempty"` -} - -// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody NewPet - // ServerInterface represents all server handlers. type ServerInterface interface { // Returns all pets @@ -150,7 +111,7 @@ func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Requ // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return @@ -176,7 +137,7 @@ func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Re // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id) + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) return diff --git a/examples/petstore-expanded/strict/api/petstore-types.gen.go b/examples/petstore-expanded/strict/api/petstore-types.gen.go new file mode 100644 index 0000000000..e3c73d101f --- /dev/null +++ b/examples/petstore-expanded/strict/api/petstore-types.gen.go @@ -0,0 +1,46 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +// Error defines model for Error. +type Error struct { + // Error code + Code int32 `json:"code"` + + // Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name of the pet + Name string `json:"name"` + + // Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // 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 `form:"tags,omitempty" json:"tags,omitempty"` + + // maximum number of results to return + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` +} + +// AddPetJSONRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody = NewPet diff --git a/examples/petstore-expanded/strict/api/petstore.go b/examples/petstore-expanded/strict/api/petstore.go index e9cf9b99ea..316425ab3d 100644 --- a/examples/petstore-expanded/strict/api/petstore.go +++ b/examples/petstore-expanded/strict/api/petstore.go @@ -1,4 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,strict-server,spec -o petstore.gen.go ../../petstore-expanded.yaml +//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 @@ -84,7 +85,7 @@ func (p *PetStore) FindPetByID(ctx context.Context, request FindPetByIDRequestOb pet, found := p.Pets[request.Id] if !found { - return FindPetByIDdefaultJSONResponse{StatusCode: http.StatusNotFound, Body:Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} + return FindPetByIDdefaultJSONResponse{StatusCode: http.StatusNotFound, Body: Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} } return FindPetByID200JSONResponse(pet) @@ -96,7 +97,7 @@ func (p *PetStore) DeletePet(ctx context.Context, request DeletePetRequestObject _, found := p.Pets[request.Id] if !found { - return DeletePetdefaultJSONResponse{StatusCode: http.StatusNotFound, Body:Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} + return DeletePetdefaultJSONResponse{StatusCode: http.StatusNotFound, Body: Error{Code: http.StatusNotFound, Message: fmt.Sprintf("Could not find pet with ID %d", request.Id)}} } delete(p.Pets, request.Id) diff --git a/examples/petstore-expanded/strict/api/server.cfg.yaml b/examples/petstore-expanded/strict/api/server.cfg.yaml new file mode 100644 index 0000000000..f3c71c63f5 --- /dev/null +++ b/examples/petstore-expanded/strict/api/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + chi-server: true + strict-server: true + embedded-spec: true +output: petstore-server.gen.go diff --git a/examples/petstore-expanded/strict/api/types.cfg.yaml b/examples/petstore-expanded/strict/api/types.cfg.yaml new file mode 100644 index 0000000000..9ac30e11e9 --- /dev/null +++ b/examples/petstore-expanded/strict/api/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: petstore-types.gen.go diff --git a/examples/petstore-expanded/strict/petstore_test.go b/examples/petstore-expanded/strict/petstore_test.go index a970ff02b2..67bbb1ac0f 100644 --- a/examples/petstore-expanded/strict/petstore_test.go +++ b/examples/petstore-expanded/strict/petstore_test.go @@ -103,12 +103,10 @@ func TestPetStore(t *testing.T) { tag := "TagOfFido" store.Pets = map[int64]api.Pet{ - 1: api.Pet{ - NewPet: api.NewPet{ - Tag: &tag, - }, + 1: { + Tag: &tag, }, - 2: api.Pet{}, + 2: {}, } // Filter pets by tag, we should have 1 diff --git a/examples/strict-server/chi/server.cfg.yaml b/examples/strict-server/chi/server.cfg.yaml new file mode 100644 index 0000000000..ca1c62c3c5 --- /dev/null +++ b/examples/strict-server/chi/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + chi-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/examples/strict-server/chi/server.gen.go b/examples/strict-server/chi/server.gen.go index 002992920f..74e8fe07fa 100644 --- a/examples/strict-server/chi/server.gen.go +++ b/examples/strict-server/chi/server.gen.go @@ -23,53 +23,6 @@ import ( "github.com/go-chi/chi/v5" ) -// Badrequest defines model for badrequest. -type Badrequest string - -// Example defines model for example. -type Example struct { - Value *string `json:"value,omitempty"` -} - -// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesTextBody string - -// TextExampleTextBody defines parameters for TextExample. -type TextExampleTextBody string - -// HeadersExampleParams defines parameters for HeadersExample. -type HeadersExampleParams struct { - Header1 string `json:"header1"` - Header2 *int `json:"header2,omitempty"` -} - -// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody Example - -// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody Example - -// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody Example - -// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody Example - -// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody Example - -// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. -type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody - -// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. -type TextExampleTextRequestBody TextExampleTextBody - -// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody Example - -// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. -type HeadersExampleJSONRequestBody Example - // ServerInterface represents all server handlers. type ServerInterface interface { @@ -427,7 +380,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse Badrequest +type JSONExample400TextResponse = Badrequest type JSONExampledefaultResponse struct { StatusCode int @@ -439,7 +392,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse Badrequest +type MultipartExample400TextResponse = Badrequest type MultipartExampledefaultResponse struct { StatusCode int @@ -470,7 +423,7 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse Badrequest +type MultipleRequestAndResponseTypes400TextResponse = Badrequest type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -478,7 +431,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse Badrequest +type TextExample400TextResponse = Badrequest type TextExampledefaultResponse struct { StatusCode int @@ -493,7 +446,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse Badrequest +type UnknownExample400TextResponse = Badrequest type UnknownExampledefaultResponse struct { StatusCode int @@ -510,7 +463,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse Badrequest +type UnspecifiedContentType400TextResponse = Badrequest type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -522,7 +475,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse Badrequest +type URLEncodedExample400TextResponse = Badrequest type URLEncodedExampledefaultResponse struct { StatusCode int @@ -547,7 +500,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse Badrequest +type HeadersExample400TextResponse = Badrequest type HeadersExampledefaultResponse struct { StatusCode int diff --git a/examples/strict-server/chi/server.go b/examples/strict-server/chi/server.go index 6ee53ea430..4252fbd3fd 100644 --- a/examples/strict-server/chi/server.go +++ b/examples/strict-server/chi/server.go @@ -1,4 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,spec,strict-server -o server.gen.go ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api diff --git a/examples/strict-server/chi/types.cfg.yaml b/examples/strict-server/chi/types.cfg.yaml new file mode 100644 index 0000000000..4ea1d8aa5b --- /dev/null +++ b/examples/strict-server/chi/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go diff --git a/examples/strict-server/chi/types.gen.go b/examples/strict-server/chi/types.gen.go new file mode 100644 index 0000000000..7e2cc737ec --- /dev/null +++ b/examples/strict-server/chi/types.gen.go @@ -0,0 +1,51 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +// Badrequest defines model for badrequest. +type Badrequest = string + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example diff --git a/examples/strict-server/client/client.cfg.yaml b/examples/strict-server/client/client.cfg.yaml new file mode 100644 index 0000000000..637450eaa2 --- /dev/null +++ b/examples/strict-server/client/client.cfg.yaml @@ -0,0 +1,5 @@ +package: api +generate: + models: true + client: true +output: client.gen.go diff --git a/examples/strict-server/client/client.gen.go b/examples/strict-server/client/client.gen.go index 178ffd04b0..29ef2a4c8e 100644 --- a/examples/strict-server/client/client.gen.go +++ b/examples/strict-server/client/client.gen.go @@ -18,7 +18,7 @@ import ( ) // Badrequest defines model for badrequest. -type Badrequest string +type Badrequest = string // Example defines model for example. type Example struct { @@ -26,10 +26,10 @@ type Example struct { } // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesTextBody string +type MultipleRequestAndResponseTypesTextBody = string // TextExampleTextBody defines parameters for TextExample. -type TextExampleTextBody string +type TextExampleTextBody = string // HeadersExampleParams defines parameters for HeadersExample. type HeadersExampleParams struct { @@ -38,31 +38,31 @@ type HeadersExampleParams struct { } // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody Example +type JSONExampleJSONRequestBody = Example // MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody Example +type MultipartExampleMultipartRequestBody = Example // MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody Example +type MultipleRequestAndResponseTypesJSONRequestBody = Example // MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody Example +type MultipleRequestAndResponseTypesFormdataRequestBody = Example // MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody Example +type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. -type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody // TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. -type TextExampleTextRequestBody TextExampleTextBody +type TextExampleTextRequestBody = TextExampleTextBody // URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody Example +type URLEncodedExampleFormdataRequestBody = Example // HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. -type HeadersExampleJSONRequestBody Example +type HeadersExampleJSONRequestBody = Example // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/examples/strict-server/client/client.go b/examples/strict-server/client/client.go index 3fa0c1e76b..3b37a171f0 100644 --- a/examples/strict-server/client/client.go +++ b/examples/strict-server/client/client.go @@ -1,3 +1,3 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,client -o client.gen.go ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=client.cfg.yaml ../strict-schema.yaml package api diff --git a/examples/strict-server/echo/server.cfg.yaml b/examples/strict-server/echo/server.cfg.yaml new file mode 100644 index 0000000000..3c3c5c9c5d --- /dev/null +++ b/examples/strict-server/echo/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + echo-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/examples/strict-server/echo/server.gen.go b/examples/strict-server/echo/server.gen.go index a33e4b4a6a..40ffc895e1 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/examples/strict-server/echo/server.gen.go @@ -23,53 +23,6 @@ import ( "github.com/labstack/echo/v4" ) -// Badrequest defines model for badrequest. -type Badrequest string - -// Example defines model for example. -type Example struct { - Value *string `json:"value,omitempty"` -} - -// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesTextBody string - -// TextExampleTextBody defines parameters for TextExample. -type TextExampleTextBody string - -// HeadersExampleParams defines parameters for HeadersExample. -type HeadersExampleParams struct { - Header1 string `json:"header1"` - Header2 *int `json:"header2,omitempty"` -} - -// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody Example - -// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody Example - -// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody Example - -// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody Example - -// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody Example - -// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. -type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody - -// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. -type TextExampleTextRequestBody TextExampleTextBody - -// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody Example - -// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. -type HeadersExampleJSONRequestBody Example - // ServerInterface represents all server handlers. type ServerInterface interface { @@ -261,7 +214,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse Badrequest +type JSONExample400TextResponse = Badrequest type JSONExampledefaultResponse struct { StatusCode int @@ -273,7 +226,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse Badrequest +type MultipartExample400TextResponse = Badrequest type MultipartExampledefaultResponse struct { StatusCode int @@ -304,7 +257,7 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse Badrequest +type MultipleRequestAndResponseTypes400TextResponse = Badrequest type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -312,7 +265,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse Badrequest +type TextExample400TextResponse = Badrequest type TextExampledefaultResponse struct { StatusCode int @@ -327,7 +280,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse Badrequest +type UnknownExample400TextResponse = Badrequest type UnknownExampledefaultResponse struct { StatusCode int @@ -344,7 +297,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse Badrequest +type UnspecifiedContentType400TextResponse = Badrequest type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -356,7 +309,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse Badrequest +type URLEncodedExample400TextResponse = Badrequest type URLEncodedExampledefaultResponse struct { StatusCode int @@ -381,7 +334,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse Badrequest +type HeadersExample400TextResponse = Badrequest type HeadersExampledefaultResponse struct { StatusCode int diff --git a/examples/strict-server/echo/server.go b/examples/strict-server/echo/server.go index 1d36c427c2..4252fbd3fd 100644 --- a/examples/strict-server/echo/server.go +++ b/examples/strict-server/echo/server.go @@ -1,4 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,server,spec,strict-server -o server.gen.go ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api diff --git a/examples/strict-server/echo/types.cfg.yaml b/examples/strict-server/echo/types.cfg.yaml new file mode 100644 index 0000000000..4ea1d8aa5b --- /dev/null +++ b/examples/strict-server/echo/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go diff --git a/examples/strict-server/gin/server.cfg.yaml b/examples/strict-server/gin/server.cfg.yaml new file mode 100644 index 0000000000..66ffd5795f --- /dev/null +++ b/examples/strict-server/gin/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + gin-server: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/examples/strict-server/gin/server.gen.go b/examples/strict-server/gin/server.gen.go index 68e0c9834c..286a908651 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/examples/strict-server/gin/server.gen.go @@ -23,53 +23,6 @@ import ( "github.com/gin-gonic/gin" ) -// Badrequest defines model for badrequest. -type Badrequest string - -// Example defines model for example. -type Example struct { - Value *string `json:"value,omitempty"` -} - -// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. -type MultipleRequestAndResponseTypesTextBody string - -// TextExampleTextBody defines parameters for TextExample. -type TextExampleTextBody string - -// HeadersExampleParams defines parameters for HeadersExample. -type HeadersExampleParams struct { - Header1 string `json:"header1"` - Header2 *int `json:"header2,omitempty"` -} - -// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. -type JSONExampleJSONRequestBody Example - -// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. -type MultipartExampleMultipartRequestBody Example - -// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. -type MultipleRequestAndResponseTypesJSONRequestBody Example - -// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. -type MultipleRequestAndResponseTypesFormdataRequestBody Example - -// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. -type MultipleRequestAndResponseTypesMultipartRequestBody Example - -// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. -type MultipleRequestAndResponseTypesTextRequestBody MultipleRequestAndResponseTypesTextBody - -// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. -type TextExampleTextRequestBody TextExampleTextBody - -// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. -type URLEncodedExampleFormdataRequestBody Example - -// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. -type HeadersExampleJSONRequestBody Example - // ServerInterface represents all server handlers. type ServerInterface interface { @@ -281,7 +234,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse Badrequest +type JSONExample400TextResponse = Badrequest type JSONExampledefaultResponse struct { StatusCode int @@ -293,7 +246,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse Badrequest +type MultipartExample400TextResponse = Badrequest type MultipartExampledefaultResponse struct { StatusCode int @@ -324,7 +277,7 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse Badrequest +type MultipleRequestAndResponseTypes400TextResponse = Badrequest type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -332,7 +285,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse Badrequest +type TextExample400TextResponse = Badrequest type TextExampledefaultResponse struct { StatusCode int @@ -347,7 +300,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse Badrequest +type UnknownExample400TextResponse = Badrequest type UnknownExampledefaultResponse struct { StatusCode int @@ -364,7 +317,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse Badrequest +type UnspecifiedContentType400TextResponse = Badrequest type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -376,7 +329,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse Badrequest +type URLEncodedExample400TextResponse = Badrequest type URLEncodedExampledefaultResponse struct { StatusCode int @@ -401,7 +354,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse Badrequest +type HeadersExample400TextResponse = Badrequest type HeadersExampledefaultResponse struct { StatusCode int diff --git a/examples/strict-server/gin/server.go b/examples/strict-server/gin/server.go index 350a480047..4252fbd3fd 100644 --- a/examples/strict-server/gin/server.go +++ b/examples/strict-server/gin/server.go @@ -1,4 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,gin,spec,strict-server -o server.gen.go ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../strict-schema.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../strict-schema.yaml package api diff --git a/examples/strict-server/gin/types.cfg.yaml b/examples/strict-server/gin/types.cfg.yaml new file mode 100644 index 0000000000..4ea1d8aa5b --- /dev/null +++ b/examples/strict-server/gin/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index 7b6f913dda..d545ec38e2 100644 --- a/internal/test/client/client.gen.go +++ b/internal/test/client/client.gen.go @@ -32,10 +32,10 @@ type SchemaObject struct { } // PostBothJSONRequestBody defines body for PostBoth for application/json ContentType. -type PostBothJSONRequestBody SchemaObject +type PostBothJSONRequestBody = SchemaObject // PostJsonJSONRequestBody defines body for PostJson for application/json ContentType. -type PostJsonJSONRequestBody SchemaObject +type PostJsonJSONRequestBody = SchemaObject // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index cd3e12a3d3..54b847791b 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -211,7 +211,7 @@ type EnsureEverythingIsReferencedJSONBody struct { } // EnsureEverythingIsReferencedTextBody defines parameters for EnsureEverythingIsReferenced. -type EnsureEverythingIsReferencedTextBody string +type EnsureEverythingIsReferencedTextBody = string // ParamsWithAddPropsParams_P1 defines parameters for ParamsWithAddProps. type ParamsWithAddPropsParams_P1 struct { @@ -251,7 +251,7 @@ type BodyWithAddPropsJSONBody_Inner struct { type EnsureEverythingIsReferencedJSONRequestBody EnsureEverythingIsReferencedJSONBody // EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for text/plain ContentType. -type EnsureEverythingIsReferencedTextRequestBody EnsureEverythingIsReferencedTextBody +type EnsureEverythingIsReferencedTextRequestBody = EnsureEverythingIsReferencedTextBody // BodyWithAddPropsJSONRequestBody defines body for BodyWithAddProps for application/json ContentType. type BodyWithAddPropsJSONRequestBody BodyWithAddPropsJSONBody @@ -851,776 +851,3 @@ func (a AdditionalPropertiesObject5) MarshalJSON() ([]byte, error) { } return json.Marshal(object) } - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer - - // A list of callbacks for modifying requests which are generated before sending over - // the network. - RequestEditors []RequestEditorFn -} - -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, - } - // mutate client and add all optional params - for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" - } - // create httpClient, if not already present - if client.Client == nil { - client.Client = &http.Client{} - } - return &client, nil -} - -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} - -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditors = append(c.RequestEditors, fn) - return nil - } -} - -// The interface specification for the client above. -type ClientInterface interface { - // EnsureEverythingIsReferenced request with any body - EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ParamsWithAddProps request - ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // BodyWithAddProps request with any body - BodyWithAddPropsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) -} - -func (c *Client) EnsureEverythingIsReferencedWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureEverythingIsReferencedRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) EnsureEverythingIsReferenced(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureEverythingIsReferencedRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) EnsureEverythingIsReferencedWithTextBody(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnsureEverythingIsReferencedRequestWithTextBody(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ParamsWithAddProps(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewParamsWithAddPropsRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) BodyWithAddPropsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewBodyWithAddPropsRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) BodyWithAddProps(ctx context.Context, body BodyWithAddPropsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewBodyWithAddPropsRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -// NewEnsureEverythingIsReferencedRequest calls the generic EnsureEverythingIsReferenced builder with application/json body -func NewEnsureEverythingIsReferencedRequest(server string, body EnsureEverythingIsReferencedJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewEnsureEverythingIsReferencedRequestWithBody(server, "application/json", bodyReader) -} - -// NewEnsureEverythingIsReferencedRequestWithTextBody calls the generic EnsureEverythingIsReferenced builder with text/plain body -func NewEnsureEverythingIsReferencedRequestWithTextBody(server string, body EnsureEverythingIsReferencedTextRequestBody) (*http.Request, error) { - var bodyReader io.Reader - bodyReader = strings.NewReader(string(body)) - return NewEnsureEverythingIsReferencedRequestWithBody(server, "text/plain", bodyReader) -} - -// NewEnsureEverythingIsReferencedRequestWithBody generates requests for EnsureEverythingIsReferenced with any type of body -func NewEnsureEverythingIsReferencedRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/ensure-everything-is-referenced") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewParamsWithAddPropsRequest generates requests for ParamsWithAddProps -func NewParamsWithAddPropsRequest(server string, params *ParamsWithAddPropsParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/params_with_add_props") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithLocation("simple", true, "p1", runtime.ParamLocationQuery, params.P1); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "p2", runtime.ParamLocationQuery, params.P2); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewBodyWithAddPropsRequest calls the generic BodyWithAddProps builder with application/json body -func NewBodyWithAddPropsRequest(server string, body BodyWithAddPropsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewBodyWithAddPropsRequestWithBody(server, "application/json", bodyReader) -} - -// NewBodyWithAddPropsRequestWithBody generates requests for BodyWithAddProps with any type of body -func NewBodyWithAddPropsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/params_with_add_props") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // EnsureEverythingIsReferenced request with any body - EnsureEverythingIsReferencedWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - - EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - - EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) - - // ParamsWithAddProps request - ParamsWithAddPropsWithResponse(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*ParamsWithAddPropsResponse, error) - - // BodyWithAddProps request with any body - BodyWithAddPropsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*BodyWithAddPropsResponse, error) - - BodyWithAddPropsWithResponse(ctx context.Context, body BodyWithAddPropsJSONRequestBody, reqEditors ...RequestEditorFn) (*BodyWithAddPropsResponse, error) -} - -type EnsureEverythingIsReferencedResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - // Has additional properties with schema for dictionaries - Five *AdditionalPropertiesObject5 `json:"five,omitempty"` - - // Has anonymous field which has additional properties - Four *AdditionalPropertiesObject4 `json:"four,omitempty"` - JsonField *ObjectWithJsonField `json:"jsonField,omitempty"` - - // Has additional properties of type int - One *AdditionalPropertiesObject1 `json:"one,omitempty"` - - // Allows any additional property - Three *AdditionalPropertiesObject3 `json:"three,omitempty"` - - // Does not allow additional properties - Two *AdditionalPropertiesObject2 `json:"two,omitempty"` - } - JSONDefault *struct { - Field SchemaObject `json:"Field"` - } -} - -// Status returns HTTPResponse.Status -func (r EnsureEverythingIsReferencedResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r EnsureEverythingIsReferencedResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type ParamsWithAddPropsResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r ParamsWithAddPropsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ParamsWithAddPropsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type BodyWithAddPropsResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r BodyWithAddPropsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r BodyWithAddPropsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// EnsureEverythingIsReferencedWithBodyWithResponse request with arbitrary body returning *EnsureEverythingIsReferencedResponse -func (c *ClientWithResponses) EnsureEverythingIsReferencedWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { - rsp, err := c.EnsureEverythingIsReferencedWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseEnsureEverythingIsReferencedResponse(rsp) -} - -func (c *ClientWithResponses) EnsureEverythingIsReferencedWithResponse(ctx context.Context, body EnsureEverythingIsReferencedJSONRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { - rsp, err := c.EnsureEverythingIsReferenced(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseEnsureEverythingIsReferencedResponse(rsp) -} - -func (c *ClientWithResponses) EnsureEverythingIsReferencedWithTextBodyWithResponse(ctx context.Context, body EnsureEverythingIsReferencedTextRequestBody, reqEditors ...RequestEditorFn) (*EnsureEverythingIsReferencedResponse, error) { - rsp, err := c.EnsureEverythingIsReferencedWithTextBody(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseEnsureEverythingIsReferencedResponse(rsp) -} - -// ParamsWithAddPropsWithResponse request returning *ParamsWithAddPropsResponse -func (c *ClientWithResponses) ParamsWithAddPropsWithResponse(ctx context.Context, params *ParamsWithAddPropsParams, reqEditors ...RequestEditorFn) (*ParamsWithAddPropsResponse, error) { - rsp, err := c.ParamsWithAddProps(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseParamsWithAddPropsResponse(rsp) -} - -// BodyWithAddPropsWithBodyWithResponse request with arbitrary body returning *BodyWithAddPropsResponse -func (c *ClientWithResponses) BodyWithAddPropsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*BodyWithAddPropsResponse, error) { - rsp, err := c.BodyWithAddPropsWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseBodyWithAddPropsResponse(rsp) -} - -func (c *ClientWithResponses) BodyWithAddPropsWithResponse(ctx context.Context, body BodyWithAddPropsJSONRequestBody, reqEditors ...RequestEditorFn) (*BodyWithAddPropsResponse, error) { - rsp, err := c.BodyWithAddProps(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseBodyWithAddPropsResponse(rsp) -} - -// ParseEnsureEverythingIsReferencedResponse parses an HTTP response from a EnsureEverythingIsReferencedWithResponse call -func ParseEnsureEverythingIsReferencedResponse(rsp *http.Response) (*EnsureEverythingIsReferencedResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &EnsureEverythingIsReferencedResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - // Has additional properties with schema for dictionaries - Five *AdditionalPropertiesObject5 `json:"five,omitempty"` - - // Has anonymous field which has additional properties - Four *AdditionalPropertiesObject4 `json:"four,omitempty"` - JsonField *ObjectWithJsonField `json:"jsonField,omitempty"` - - // Has additional properties of type int - One *AdditionalPropertiesObject1 `json:"one,omitempty"` - - // Allows any additional property - Three *AdditionalPropertiesObject3 `json:"three,omitempty"` - - // Does not allow additional properties - Two *AdditionalPropertiesObject2 `json:"two,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest struct { - Field SchemaObject `json:"Field"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSONDefault = &dest - - case true: - // Content-type (text/plain) unsupported - - } - - return response, nil -} - -// ParseParamsWithAddPropsResponse parses an HTTP response from a ParamsWithAddPropsWithResponse call -func ParseParamsWithAddPropsResponse(rsp *http.Response) (*ParamsWithAddPropsResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ParamsWithAddPropsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - return response, nil -} - -// ParseBodyWithAddPropsResponse parses an HTTP response from a BodyWithAddPropsWithResponse call -func ParseBodyWithAddPropsResponse(rsp *http.Response) (*BodyWithAddPropsResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &BodyWithAddPropsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - return response, nil -} - -// ServerInterface represents all server handlers. -type ServerInterface interface { - - // (GET /ensure-everything-is-referenced) - EnsureEverythingIsReferenced(ctx echo.Context) error - - // (GET /params_with_add_props) - ParamsWithAddProps(ctx echo.Context, params ParamsWithAddPropsParams) error - - // (POST /params_with_add_props) - BodyWithAddProps(ctx echo.Context) error -} - -// ServerInterfaceWrapper converts echo contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface -} - -// EnsureEverythingIsReferenced converts echo context to params. -func (w *ServerInterfaceWrapper) EnsureEverythingIsReferenced(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.EnsureEverythingIsReferenced(ctx) - return err -} - -// ParamsWithAddProps converts echo context to params. -func (w *ServerInterfaceWrapper) ParamsWithAddProps(ctx echo.Context) error { - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params ParamsWithAddPropsParams - // ------------- Required query parameter "p1" ------------- - - err = runtime.BindQueryParameter("simple", true, true, "p1", ctx.QueryParams(), ¶ms.P1) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter p1: %s", err)) - } - - // ------------- Required query parameter "p2" ------------- - - err = runtime.BindQueryParameter("form", true, true, "p2", ctx.QueryParams(), ¶ms.P2) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter p2: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.ParamsWithAddProps(ctx, params) - return err -} - -// BodyWithAddProps converts echo context to params. -func (w *ServerInterfaceWrapper) BodyWithAddProps(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.BodyWithAddProps(ctx) - return err -} - -// This is a simple interface which specifies echo.Route addition functions which -// are present on both echo.Echo and echo.Group, since we want to allow using -// either of them for path registration -type EchoRouter interface { - CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route -} - -// RegisterHandlers adds each server route to the EchoRouter. -func RegisterHandlers(router EchoRouter, si ServerInterface) { - RegisterHandlersWithBaseURL(router, si, "") -} - -// Registers handlers, and prepends BaseURL to the paths, so that the paths -// can be served under a prefix. -func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { - - wrapper := ServerInterfaceWrapper{ - Handler: si, - } - - router.GET(baseURL+"/ensure-everything-is-referenced", wrapper.EnsureEverythingIsReferenced) - router.GET(baseURL+"/params_with_add_props", wrapper.ParamsWithAddProps) - router.POST(baseURL+"/params_with_add_props", wrapper.BodyWithAddProps) - -} - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "H4sIAAAAAAAC/9RXTW/cNhD9KwO2R9nrj/SyNxdNURdoEzgGeogNgxZHFl3uUCEpb4RA/70YStqVtdRm", - "bedSX6ylyPl4M/P49E3kdlVZQgpeLL8Jh19q9OFXqzTGhavNQsM/c0sBKfCjrCqjcxm0pcWjt8RrPi9x", - "JfmpcrZCF3orv2s0ih9+dliIpfhpsXW76A75xaf4/8P9I+ZBtG0Wg9EOlVh+7i3c8nLAr2FRGaknLkNT", - "oVgKH5ymB9HyH9vwlSU/JNP96H383/LJhEKfO11xjGIpLsDrVWUQhiTBbp31UbChC6U0H5Hm4yaLLqzT", - "mHji9ci/poAP6MSO+z+kh+1Z2CIEtgA+DJqCyCbQaZW2TXKFiawzYavOQQqS55hGExl7uM2GrQMi2R4U", - "zuZRKKTxOE38N4seyAaQxth1GoO35v2DUjufTy24eiezC07Ig6QmkVWzk9MLYn9Z2O9eFnbsRLLUrGzt", - "oeDRgnWp8xLKuR7drQ8Ruu+5/aHpH3a8iyt7DYq/7Jvuw5nr8Llf61BCZwQK60DpPG5yHeA7oXce/tGh", - "/NNb2pDqQShn4kmaGiODFdatZBBLEXk7m9l6dsDW9Nj1nlLoP4NqJ/ZCOx/+nkvAoVQfyDRXvUcuEG98", - "Dvd1qf1mBEF7GAIESQoGGxl4C6FEeLCwsgoN+NLWRkEpnxB0AC4aVJaZx2VwQ9Lz6lobE6nsntk6N7VC", - "Hh0kfqk9eKQAhbMryI3m52DBo3tCd3zD8A3uhwHZzdGadPJrpwOmsp9Q5Ghn52Rao+ghG0E9A+ycy9t4", - "s2oqLHs3OkfyuG088dflNccbdOBMxDX6AJ8iBNwZ6HxXptPjk+OT7r5CkpUWS3F+fHJ8ykQjQxnbYYHk", - "a4dH+ISuCaWmhyPtjxwW6JByjM3/gGGmB5BUrB/gV+2D7wouA2wHGHJJXMncoQyoQBOEUvsb8hXmsV36", - "UleuJlSxgtxXUfRcKrEU72OA7zfxXfqrbXTZSB42cxzyTEEuxvJxqsbOTk7eIMEK/YTf47F91NhmorC1", - "e72Jd2ziccxb++ykqI6bhd6QxGnsy9LhG2ycRxtr+3oLZ5E1J8TYq9VC1ibMd0rfDIuJLu9OLyrp5Mrf", - "8aVyJ5W64/r72RG5AB6z7gqKJzGg87HpJdxb1fSKoKeO9A2WmIiPMQou3IWKhBGlw8aBWH5OE/awY16C", - "RGcs+sWXGl0z3PFLUZ2KMcV1zLqdg30CZed+8qGJtNV9KYg2OyRaGqmpqD+2908HIiEqz3fBPd5QqB1F", - "sgmWr5i4s9P/rAFS0fLJtXX/ziNwtheBFym3xN0zbdaU4rpt29tnhEW1MW0mKusT3Rc1EfTcN243Zjep", - "id8q7TAPSUAy7tMb2gs8N3bqbKJnmW4nHete+R1/uBo+tAyjb59XSuLhY2ioUzvtlXa3cG3b/hcAAP//", - "Jit1A+wQAAA=", -} - -// GetSwagger returns the content of the embedded swagger specification file -// or error if failed to decode -func decodeSpec() ([]byte, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - - return buf.Bytes(), nil -} - -var rawSpec = decodeSpecCached() - -// a naive cached of a decoded swagger spec -func decodeSpecCached() func() ([]byte, error) { - data, err := decodeSpec() - return func() ([]byte, error) { - return data, err - } -} - -// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. -func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) - if len(pathToFile) > 0 { - res[pathToFile] = rawSpec - } - - return res -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. The external references of Swagger specification are resolved. -// The logic of resolving external references is tightly connected to "import-mapping" feature. -// Externally referenced files must be embedded in the corresponding golang packages. -// Urls can be supported but this task was out of the scope. -func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() - pathToFile = path.Clean(pathToFile) - getSpec, ok := resolvePath[pathToFile] - if !ok { - err1 := fmt.Errorf("path not found: %s", pathToFile) - return nil, err1 - } - return getSpec() - } - var specData []byte - specData, err = rawSpec() - if err != nil { - return - } - swagger, err = loader.LoadFromData(specData) - if err != nil { - return - } - return -} diff --git a/internal/test/issues/issue-312/issue.gen.go b/internal/test/issues/issue-312/issue.gen.go index a55d368906..3f4c9183bc 100644 --- a/internal/test/issues/issue-312/issue.gen.go +++ b/internal/test/issues/issue-312/issue.gen.go @@ -44,7 +44,7 @@ type PetNames struct { } // ValidatePetsJSONRequestBody defines body for ValidatePets for application/json ContentType. -type ValidatePetsJSONRequestBody PetNames +type ValidatePetsJSONRequestBody = PetNames // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go index b7d27631c4..c80306102b 100644 --- a/internal/test/schemas/schemas.gen.go +++ b/internal/test/schemas/schemas.gen.go @@ -80,7 +80,7 @@ type Issue9Params struct { } // Issue185JSONRequestBody defines body for Issue185 for application/json ContentType. -type Issue185JSONRequestBody NullableProperties +type Issue185JSONRequestBody = NullableProperties // Issue9JSONRequestBody defines body for Issue9 for application/json ContentType. type Issue9JSONRequestBody = Issue9JSONBody diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go index 9975a98c5c..ba3f3de87c 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -113,10 +113,10 @@ type UpdateResource3JSONBody struct { } // CreateResourceJSONRequestBody defines body for CreateResource for application/json ContentType. -type CreateResourceJSONRequestBody EveryTypeRequired +type CreateResourceJSONRequestBody = EveryTypeRequired // CreateResource2JSONRequestBody defines body for CreateResource2 for application/json ContentType. -type CreateResource2JSONRequestBody Resource +type CreateResource2JSONRequestBody = Resource // UpdateResource3JSONRequestBody defines body for UpdateResource3 for application/json ContentType. type UpdateResource3JSONRequestBody UpdateResource3JSONBody diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index bf470e2eec..e1836ec927 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -177,7 +177,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } var strictServerOut string - if opts.GenerateStrict { + if opts.Generate.Strict { strictServerOut, err = GenerateStrictServer(t, ops, opts) if err != nil { return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) @@ -264,7 +264,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } - if opts.GenerateStrict { + if opts.Generate.Strict { _, err = w.WriteString(strictServerOut) if err != nil { return "", fmt.Errorf("error writing server path handlers: %w", err) diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 084ab5b7f7..4826bb5365 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -19,6 +19,7 @@ type GenerateOptions struct { ChiServer bool `yaml:"chi-server,omitempty"` // ChiServer specifies whether to generate chi server boilerplate EchoServer bool `yaml:"echo-server,omitempty"` // EchoServer specifies whether to generate echo server boilerplate GinServer bool `yaml:"gin-server,omitempty"` // GinServer specifies whether to generate echo server boilerplate + Strict bool `yaml:"strict-server,omitempty"` // Strict specifies whether to generate strict server wrapper Client bool `yaml:"client,omitempty"` // Client specifies whether to generate client boilerplate Models bool `yaml:"models,omitempty"` // Models specifies whether to generate type definitions EmbeddedSpec bool `yaml:"embedded-spec,omitempty"` // Whether to embed the swagger spec in the generated code diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 393ccaddd7..f8cc4248d4 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -883,15 +883,15 @@ func GenerateGinServer(t *template.Template, operations []OperationDefinition) ( return GenerateTemplates([]string{"gin/gin-interface.tmpl", "gin/gin-wrappers.tmpl", "gin/gin-register.tmpl"}, t, operations) } -func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Options) (string, error) { +func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Configuration) (string, error) { templates := []string{"strict/strict-interface.tmpl"} - if opts.GenerateChiServer { + if opts.Generate.ChiServer { templates = append(templates, "strict/strict-chi.tmpl") } - if opts.GenerateEchoServer { + if opts.Generate.EchoServer { templates = append(templates, "strict/strict-echo.tmpl") } - if opts.GenerateGinServer { + if opts.Generate.GinServer { templates = append(templates, "strict/strict-gin.tmpl") } return GenerateTemplates(templates, t, operations) diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 90d861c2e0..b71656d76d 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -31,9 +31,9 @@ {{range .Contents}} {{if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and (opts.AliasTypes) (.Schema.IsRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} - {{if and (not (and (opts.AliasTypes) (.Schema.IsRef))) (eq .NameTag "JSON")}} + {{if and (not (and .Schema.IsRef)) (eq .NameTag "JSON")}} func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { return json.Marshal(({{.Schema.GoType}})(t)) } From 57e8fa032faab5c7e8b39da45e5eaea3189078b2 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 22 May 2022 21:41:33 +0300 Subject: [PATCH 15/19] Reuse responses defined in components section, moved strict test to tests --- .../test}/strict-server/chi/server.cfg.yaml | 0 .../test}/strict-server/chi/server.gen.go | 169 +++++++++++++----- .../test/strict-server/chi}/server.go | 6 +- .../test}/strict-server/chi/types.cfg.yaml | 0 .../test}/strict-server/chi/types.gen.go | 9 +- .../strict-server/client/client.cfg.yaml | 0 .../test}/strict-server/client/client.gen.go | 148 ++++++++++++++- .../test}/strict-server/client/client.go | 0 .../test}/strict-server/echo/server.cfg.yaml | 0 .../test}/strict-server/echo/server.gen.go | 159 ++++++++++++---- .../test/strict-server/echo}/server.go | 6 +- .../test}/strict-server/echo/types.cfg.yaml | 0 internal/test/strict-server/echo/types.gen.go | 54 ++++++ .../test}/strict-server/gin/server.cfg.yaml | 0 .../test}/strict-server/gin/server.gen.go | 161 +++++++++++++---- .../test}/strict-server/gin/server.go | 6 +- .../test}/strict-server/gin/types.cfg.yaml | 0 internal/test/strict-server/gin/types.gen.go | 54 ++++++ .../test}/strict-server/strict-schema.yaml | 32 +++- .../test}/strict-server/strict_test.go | 57 +++--- pkg/codegen/codegen.go | 9 + pkg/codegen/operations.go | 33 ++-- .../templates/strict/strict-interface.tmpl | 44 +++-- .../templates/strict/strict-responses.tmpl | 52 ++++++ 24 files changed, 811 insertions(+), 188 deletions(-) rename {examples => internal/test}/strict-server/chi/server.cfg.yaml (100%) rename {examples => internal/test}/strict-server/chi/server.gen.go (86%) rename {examples/strict-server/echo => internal/test/strict-server/chi}/server.go (93%) rename {examples => internal/test}/strict-server/chi/types.cfg.yaml (100%) rename {examples => internal/test}/strict-server/chi/types.gen.go (90%) rename {examples => internal/test}/strict-server/client/client.cfg.yaml (100%) rename {examples => internal/test}/strict-server/client/client.gen.go (89%) rename {examples => internal/test}/strict-server/client/client.go (100%) rename {examples => internal/test}/strict-server/echo/server.cfg.yaml (100%) rename {examples => internal/test}/strict-server/echo/server.gen.go (83%) rename {examples/strict-server/chi => internal/test/strict-server/echo}/server.go (93%) rename {examples => internal/test}/strict-server/echo/types.cfg.yaml (100%) create mode 100644 internal/test/strict-server/echo/types.gen.go rename {examples => internal/test}/strict-server/gin/server.cfg.yaml (100%) rename {examples => internal/test}/strict-server/gin/server.gen.go (83%) rename {examples => internal/test}/strict-server/gin/server.go (93%) rename {examples => internal/test}/strict-server/gin/types.cfg.yaml (100%) create mode 100644 internal/test/strict-server/gin/types.gen.go rename {examples => internal/test}/strict-server/strict-schema.yaml (88%) rename {examples => internal/test}/strict-server/strict_test.go (82%) create mode 100644 pkg/codegen/templates/strict/strict-responses.tmpl diff --git a/examples/strict-server/chi/server.cfg.yaml b/internal/test/strict-server/chi/server.cfg.yaml similarity index 100% rename from examples/strict-server/chi/server.cfg.yaml rename to internal/test/strict-server/chi/server.cfg.yaml diff --git a/examples/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go similarity index 86% rename from examples/strict-server/chi/server.gen.go rename to internal/test/strict-server/chi/server.gen.go index 74e8fe07fa..43a84f88b4 100644 --- a/examples/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -35,6 +35,9 @@ type ServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) + // (POST /reusable-responses) + ReusableResponses(w http.ResponseWriter, r *http.Request) + // (POST /text) TextExample(w http.ResponseWriter, r *http.Request) @@ -105,6 +108,21 @@ func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.Respon handler(w, r.WithContext(ctx)) } +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler = func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReusableResponses(w, r) + } + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler(w, r.WithContext(ctx)) +} + // TextExample operation middleware func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -351,6 +369,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/text", wrapper.TextExample) }) @@ -370,6 +391,23 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl return r } +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +func (t ReusableresponseJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + type JSONExampleRequestObject struct { Body *JSONExampleJSONRequestBody } @@ -380,7 +418,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse = Badrequest +type JSONExample400Response = BadrequestResponse type JSONExampledefaultResponse struct { StatusCode int @@ -392,7 +430,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse = Badrequest +type MultipartExample400Response = BadrequestResponse type MultipartExampledefaultResponse struct { StatusCode int @@ -423,7 +461,19 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse = Badrequest +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -431,7 +481,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse = Badrequest +type TextExample400Response = BadrequestResponse type TextExampledefaultResponse struct { StatusCode int @@ -446,7 +496,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse = Badrequest +type UnknownExample400Response = BadrequestResponse type UnknownExampledefaultResponse struct { StatusCode int @@ -463,7 +513,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse = Badrequest +type UnspecifiedContentType400Response = BadrequestResponse type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -475,7 +525,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse = Badrequest +type URLEncodedExample400Response = BadrequestResponse type URLEncodedExampledefaultResponse struct { StatusCode int @@ -500,7 +550,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse = Badrequest +type HeadersExample400Response = BadrequestResponse type HeadersExampledefaultResponse struct { StatusCode int @@ -518,6 +568,9 @@ type StrictServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + // (POST /text) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} @@ -572,10 +625,8 @@ func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) writeJSON(w, v) - case JSONExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case JSONExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case JSONExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -615,10 +666,8 @@ func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request if err := v(writer); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } - case MultipartExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case MultipartExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case MultipartExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -718,10 +767,47 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter, w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) writeRaw(w, ([]byte)(v)) - case MultipleRequestAndResponseTypes400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case MultipleRequestAndResponseTypes400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) + case error: + http.Error(w, v.Error(), http.StatusInternalServerError) + case nil: + default: + http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError) + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Request) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) interface{} { + return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response := handler(r.Context(), w, r, request) + + switch v := response.(type) { + case ReusableResponses200JSONResponse: + w.Header().Set("header1", fmt.Sprint(v.Headers.Header1)) + w.Header().Set("header2", fmt.Sprint(v.Headers.Header2)) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + writeJSON(w, v) + case ReusableResponses400Response: + w.WriteHeader(400) + case ReusableResponsesdefaultResponse: + w.WriteHeader(v.StatusCode) case error: http.Error(w, v.Error(), http.StatusInternalServerError) case nil: @@ -756,10 +842,8 @@ func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) writeRaw(w, ([]byte)(v)) - case TextExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case TextExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case TextExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -796,10 +880,8 @@ func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request) defer closer.Close() } _, _ = io.Copy(w, v.Body) - case UnknownExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case UnknownExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case UnknownExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -838,10 +920,8 @@ func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.R defer closer.Close() } _, _ = io.Copy(w, v.Body) - case UnspecifiedContentType400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case UnspecifiedContentType400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case UnspecifiedContentTypedefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -885,10 +965,8 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques } else { writeRaw(w, []byte(form.Encode())) } - case URLEncodedExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case URLEncodedExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case URLEncodedExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -928,10 +1006,8 @@ func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request, w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) writeJSON(w, v) - case HeadersExample400TextResponse: - w.Header().Set("Content-Type", "text/plain") + case HeadersExample400Response: w.WriteHeader(400) - writeRaw(w, ([]byte)(v)) case HeadersExampledefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -957,20 +1033,21 @@ func writeRaw(w http.ResponseWriter, b []byte) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", - "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", - "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", - "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", - "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", - "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", - "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", - "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", - "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", - "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", - "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", - "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", - "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", - "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", + "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", + "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", + "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", + "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", + "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", + "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", + "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", + "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", + "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", + "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", + "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", + "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", + "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", + "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", + "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/echo/server.go b/internal/test/strict-server/chi/server.go similarity index 93% rename from examples/strict-server/echo/server.go rename to internal/test/strict-server/chi/server.go index 4252fbd3fd..876850a97c 100644 --- a/examples/strict-server/echo/server.go +++ b/internal/test/strict-server/chi/server.go @@ -73,7 +73,7 @@ func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, reque } }) default: - return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + return MultipleRequestAndResponseTypes400Response{} } } @@ -96,3 +96,7 @@ func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedE func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} } + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/examples/strict-server/chi/types.cfg.yaml b/internal/test/strict-server/chi/types.cfg.yaml similarity index 100% rename from examples/strict-server/chi/types.cfg.yaml rename to internal/test/strict-server/chi/types.cfg.yaml diff --git a/examples/strict-server/chi/types.gen.go b/internal/test/strict-server/chi/types.gen.go similarity index 90% rename from examples/strict-server/chi/types.gen.go rename to internal/test/strict-server/chi/types.gen.go index 7e2cc737ec..33827cb7a4 100644 --- a/examples/strict-server/chi/types.gen.go +++ b/internal/test/strict-server/chi/types.gen.go @@ -3,14 +3,14 @@ // Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. package api -// Badrequest defines model for badrequest. -type Badrequest = string - // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` } +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string @@ -41,6 +41,9 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + // TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. type TextExampleTextRequestBody = TextExampleTextBody diff --git a/examples/strict-server/client/client.cfg.yaml b/internal/test/strict-server/client/client.cfg.yaml similarity index 100% rename from examples/strict-server/client/client.cfg.yaml rename to internal/test/strict-server/client/client.cfg.yaml diff --git a/examples/strict-server/client/client.gen.go b/internal/test/strict-server/client/client.gen.go similarity index 89% rename from examples/strict-server/client/client.gen.go rename to internal/test/strict-server/client/client.gen.go index 29ef2a4c8e..6c02ddf59c 100644 --- a/examples/strict-server/client/client.gen.go +++ b/internal/test/strict-server/client/client.gen.go @@ -17,14 +17,14 @@ import ( "github.com/deepmap/oapi-codegen/pkg/runtime" ) -// Badrequest defines model for badrequest. -type Badrequest = string - // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` } +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + // MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. type MultipleRequestAndResponseTypesTextBody = string @@ -55,6 +55,9 @@ type MultipleRequestAndResponseTypesMultipartRequestBody = Example // MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + // TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. type TextExampleTextRequestBody = TextExampleTextBody @@ -154,6 +157,11 @@ type ClientInterface interface { MultipleRequestAndResponseTypesWithTextBody(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ReusableResponses request with any body + ReusableResponsesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + ReusableResponses(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // TextExample request with any body TextExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -260,6 +268,30 @@ func (c *Client) MultipleRequestAndResponseTypesWithTextBody(ctx context.Context return c.Client.Do(req) } +func (c *Client) ReusableResponsesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReusableResponsesRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ReusableResponses(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReusableResponsesRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) TextExampleWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewTextExampleRequestWithBody(c.Server, contentType, body) if err != nil { @@ -483,6 +515,46 @@ func NewMultipleRequestAndResponseTypesRequestWithBody(server string, contentTyp return req, nil } +// NewReusableResponsesRequest calls the generic ReusableResponses builder with application/json body +func NewReusableResponsesRequest(server string, body ReusableResponsesJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewReusableResponsesRequestWithBody(server, "application/json", bodyReader) +} + +// NewReusableResponsesRequestWithBody generates requests for ReusableResponses with any type of body +func NewReusableResponsesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/reusable-responses") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewTextExampleRequestWithTextBody calls the generic TextExample builder with text/plain body func NewTextExampleRequestWithTextBody(server string, body TextExampleTextRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -737,6 +809,11 @@ type ClientWithResponsesInterface interface { MultipleRequestAndResponseTypesWithTextBodyWithResponse(ctx context.Context, body MultipleRequestAndResponseTypesTextRequestBody, reqEditors ...RequestEditorFn) (*MultipleRequestAndResponseTypesResponse, error) + // ReusableResponses request with any body + ReusableResponsesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) + + ReusableResponsesWithResponse(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) + // TextExample request with any body TextExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) @@ -824,6 +901,28 @@ func (r MultipleRequestAndResponseTypesResponse) StatusCode() int { return 0 } +type ReusableResponsesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Example +} + +// Status returns HTTPResponse.Status +func (r ReusableResponsesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ReusableResponsesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type TextExampleResponse struct { Body []byte HTTPResponse *http.Response @@ -989,6 +1088,23 @@ func (c *ClientWithResponses) MultipleRequestAndResponseTypesWithTextBodyWithRes return ParseMultipleRequestAndResponseTypesResponse(rsp) } +// ReusableResponsesWithBodyWithResponse request with arbitrary body returning *ReusableResponsesResponse +func (c *ClientWithResponses) ReusableResponsesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) { + rsp, err := c.ReusableResponsesWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseReusableResponsesResponse(rsp) +} + +func (c *ClientWithResponses) ReusableResponsesWithResponse(ctx context.Context, body ReusableResponsesJSONRequestBody, reqEditors ...RequestEditorFn) (*ReusableResponsesResponse, error) { + rsp, err := c.ReusableResponses(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseReusableResponsesResponse(rsp) +} + // TextExampleWithBodyWithResponse request with arbitrary body returning *TextExampleResponse func (c *ClientWithResponses) TextExampleWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TextExampleResponse, error) { rsp, err := c.TextExampleWithBody(ctx, contentType, body, reqEditors...) @@ -1129,6 +1245,32 @@ func ParseMultipleRequestAndResponseTypesResponse(rsp *http.Response) (*Multiple return response, nil } +// ParseReusableResponsesResponse parses an HTTP response from a ReusableResponsesWithResponse call +func ParseReusableResponsesResponse(rsp *http.Response) (*ReusableResponsesResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ReusableResponsesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Example + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseTextExampleResponse parses an HTTP response from a TextExampleWithResponse call func ParseTextExampleResponse(rsp *http.Response) (*TextExampleResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) diff --git a/examples/strict-server/client/client.go b/internal/test/strict-server/client/client.go similarity index 100% rename from examples/strict-server/client/client.go rename to internal/test/strict-server/client/client.go diff --git a/examples/strict-server/echo/server.cfg.yaml b/internal/test/strict-server/echo/server.cfg.yaml similarity index 100% rename from examples/strict-server/echo/server.cfg.yaml rename to internal/test/strict-server/echo/server.cfg.yaml diff --git a/examples/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go similarity index 83% rename from examples/strict-server/echo/server.gen.go rename to internal/test/strict-server/echo/server.gen.go index 40ffc895e1..32eef904eb 100644 --- a/examples/strict-server/echo/server.gen.go +++ b/internal/test/strict-server/echo/server.gen.go @@ -35,6 +35,9 @@ type ServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(ctx echo.Context) error + // (POST /reusable-responses) + ReusableResponses(ctx echo.Context) error + // (POST /text) TextExample(ctx echo.Context) error @@ -83,6 +86,15 @@ func (w *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(ctx echo.Contex return err } +// ReusableResponses converts echo context to params. +func (w *ServerInterfaceWrapper) ReusableResponses(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.ReusableResponses(ctx) + return err +} + // TextExample converts echo context to params. func (w *ServerInterfaceWrapper) TextExample(ctx echo.Context) error { var err error @@ -196,6 +208,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/json", wrapper.JSONExample) router.POST(baseURL+"/multipart", wrapper.MultipartExample) router.POST(baseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.POST(baseURL+"/reusable-responses", wrapper.ReusableResponses) router.POST(baseURL+"/text", wrapper.TextExample) router.POST(baseURL+"/unknown", wrapper.UnknownExample) router.POST(baseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) @@ -204,6 +217,23 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +func (t ReusableresponseJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + type JSONExampleRequestObject struct { Body *JSONExampleJSONRequestBody } @@ -214,7 +244,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse = Badrequest +type JSONExample400Response = BadrequestResponse type JSONExampledefaultResponse struct { StatusCode int @@ -226,7 +256,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse = Badrequest +type MultipartExample400Response = BadrequestResponse type MultipartExampledefaultResponse struct { StatusCode int @@ -257,7 +287,19 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse = Badrequest +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -265,7 +307,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse = Badrequest +type TextExample400Response = BadrequestResponse type TextExampledefaultResponse struct { StatusCode int @@ -280,7 +322,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse = Badrequest +type UnknownExample400Response = BadrequestResponse type UnknownExampledefaultResponse struct { StatusCode int @@ -297,7 +339,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse = Badrequest +type UnspecifiedContentType400Response = BadrequestResponse type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -309,7 +351,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse = Badrequest +type URLEncodedExample400Response = BadrequestResponse type URLEncodedExampledefaultResponse struct { StatusCode int @@ -334,7 +376,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse = Badrequest +type HeadersExample400Response = BadrequestResponse type HeadersExampledefaultResponse struct { StatusCode int @@ -352,6 +394,9 @@ type StrictServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + // (POST /text) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} @@ -403,8 +448,8 @@ func (sh *strictHandler) JSONExample(ctx echo.Context) error { switch v := response.(type) { case JSONExample200JSONResponse: return ctx.JSON(200, v) - case JSONExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case JSONExample400Response: + return ctx.NoContent(400) case JSONExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -443,8 +488,8 @@ func (sh *strictHandler) MultipartExample(ctx echo.Context) error { if err := v(writer); err != nil { return err } - case MultipartExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case MultipartExample400Response: + return ctx.NoContent(400) case MultipartExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -532,8 +577,45 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx echo.Context) error } case MultipleRequestAndResponseTypes200TextResponse: return ctx.Blob(200, "text/plain", []byte(v)) - case MultipleRequestAndResponseTypes400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes400Response: + return ctx.NoContent(400) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + return nil +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx echo.Context) error { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) interface{} { + return sh.ssi.ReusableResponses(ctx.Request().Context(), request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case ReusableResponses200JSONResponse: + ctx.Response().Header().Set("header1", fmt.Sprint(v.Headers.Header1)) + ctx.Response().Header().Set("header2", fmt.Sprint(v.Headers.Header2)) + return ctx.JSON(200, v) + case ReusableResponses400Response: + return ctx.NoContent(400) + case ReusableResponsesdefaultResponse: + return ctx.NoContent(v.StatusCode) case error: return v case nil: @@ -566,8 +648,8 @@ func (sh *strictHandler) TextExample(ctx echo.Context) error { switch v := response.(type) { case TextExample200TextResponse: return ctx.Blob(200, "text/plain", []byte(v)) - case TextExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case TextExample400Response: + return ctx.NoContent(400) case TextExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -603,8 +685,8 @@ func (sh *strictHandler) UnknownExample(ctx echo.Context) error { defer closer.Close() } return ctx.Stream(200, "video/mp4", v.Body) - case UnknownExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case UnknownExample400Response: + return ctx.NoContent(400) case UnknownExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -642,8 +724,8 @@ func (sh *strictHandler) UnspecifiedContentType(ctx echo.Context) error { defer closer.Close() } return ctx.Stream(200, v.ContentType, v.Body) - case UnspecifiedContentType400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case UnspecifiedContentType400Response: + return ctx.NoContent(400) case UnspecifiedContentTypedefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -685,8 +767,8 @@ func (sh *strictHandler) URLEncodedExample(ctx echo.Context) error { } else { return ctx.Blob(200, "application/x-www-form-urlencoded", []byte(form.Encode())) } - case URLEncodedExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case URLEncodedExample400Response: + return ctx.NoContent(400) case URLEncodedExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -724,8 +806,8 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP ctx.Response().Header().Set("header1", fmt.Sprint(v.Headers.Header1)) ctx.Response().Header().Set("header2", fmt.Sprint(v.Headers.Header2)) return ctx.JSON(200, v) - case HeadersExample400TextResponse: - return ctx.Blob(400, "text/plain", []byte(v)) + case HeadersExample400Response: + return ctx.NoContent(400) case HeadersExampledefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -740,20 +822,21 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", - "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", - "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", - "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", - "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", - "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", - "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", - "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", - "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", - "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", - "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", - "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", - "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", - "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", + "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", + "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", + "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", + "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", + "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", + "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", + "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", + "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", + "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", + "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", + "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", + "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", + "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", + "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", + "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/chi/server.go b/internal/test/strict-server/echo/server.go similarity index 93% rename from examples/strict-server/chi/server.go rename to internal/test/strict-server/echo/server.go index 4252fbd3fd..876850a97c 100644 --- a/examples/strict-server/chi/server.go +++ b/internal/test/strict-server/echo/server.go @@ -73,7 +73,7 @@ func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, reque } }) default: - return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + return MultipleRequestAndResponseTypes400Response{} } } @@ -96,3 +96,7 @@ func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedE func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} } + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/examples/strict-server/echo/types.cfg.yaml b/internal/test/strict-server/echo/types.cfg.yaml similarity index 100% rename from examples/strict-server/echo/types.cfg.yaml rename to internal/test/strict-server/echo/types.cfg.yaml diff --git a/internal/test/strict-server/echo/types.gen.go b/internal/test/strict-server/echo/types.gen.go new file mode 100644 index 0000000000..33827cb7a4 --- /dev/null +++ b/internal/test/strict-server/echo/types.gen.go @@ -0,0 +1,54 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example diff --git a/examples/strict-server/gin/server.cfg.yaml b/internal/test/strict-server/gin/server.cfg.yaml similarity index 100% rename from examples/strict-server/gin/server.cfg.yaml rename to internal/test/strict-server/gin/server.cfg.yaml diff --git a/examples/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go similarity index 83% rename from examples/strict-server/gin/server.gen.go rename to internal/test/strict-server/gin/server.gen.go index 286a908651..939295211e 100644 --- a/examples/strict-server/gin/server.gen.go +++ b/internal/test/strict-server/gin/server.gen.go @@ -35,6 +35,9 @@ type ServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(c *gin.Context) + // (POST /reusable-responses) + ReusableResponses(c *gin.Context) + // (POST /text) TextExample(c *gin.Context) @@ -89,6 +92,16 @@ func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *gin.Contex siw.Handler.MultipleRequestAndResponseTypes(c) } +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.ReusableResponses(c) +} + // TextExample operation middleware func (siw *ServerInterfaceWrapper) TextExample(c *gin.Context) { @@ -211,6 +224,8 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options router.POST(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.POST(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) + router.POST(options.BaseURL+"/text", wrapper.TextExample) router.POST(options.BaseURL+"/unknown", wrapper.UnknownExample) @@ -224,6 +239,23 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options return router } +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +func (t ReusableresponseJSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + type JSONExampleRequestObject struct { Body *JSONExampleJSONRequestBody } @@ -234,7 +266,7 @@ func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal((Example)(t)) } -type JSONExample400TextResponse = Badrequest +type JSONExample400Response = BadrequestResponse type JSONExampledefaultResponse struct { StatusCode int @@ -246,7 +278,7 @@ type MultipartExampleRequestObject struct { type MultipartExample200MultipartResponse func(writer *multipart.Writer) error -type MultipartExample400TextResponse = Badrequest +type MultipartExample400Response = BadrequestResponse type MultipartExampledefaultResponse struct { StatusCode int @@ -277,7 +309,19 @@ type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart. type MultipleRequestAndResponseTypes200TextResponse string -type MultipleRequestAndResponseTypes400TextResponse = Badrequest +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} type TextExampleRequestObject struct { Body *TextExampleTextRequestBody @@ -285,7 +329,7 @@ type TextExampleRequestObject struct { type TextExample200TextResponse string -type TextExample400TextResponse = Badrequest +type TextExample400Response = BadrequestResponse type TextExampledefaultResponse struct { StatusCode int @@ -300,7 +344,7 @@ type UnknownExample200Videomp4Response struct { ContentLength int64 } -type UnknownExample400TextResponse = Badrequest +type UnknownExample400Response = BadrequestResponse type UnknownExampledefaultResponse struct { StatusCode int @@ -317,7 +361,7 @@ type UnspecifiedContentType200VideoResponse struct { ContentLength int64 } -type UnspecifiedContentType400TextResponse = Badrequest +type UnspecifiedContentType400Response = BadrequestResponse type UnspecifiedContentTypedefaultResponse struct { StatusCode int @@ -329,7 +373,7 @@ type URLEncodedExampleRequestObject struct { type URLEncodedExample200FormdataResponse Example -type URLEncodedExample400TextResponse = Badrequest +type URLEncodedExample400Response = BadrequestResponse type URLEncodedExampledefaultResponse struct { StatusCode int @@ -354,7 +398,7 @@ func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } -type HeadersExample400TextResponse = Badrequest +type HeadersExample400Response = BadrequestResponse type HeadersExampledefaultResponse struct { StatusCode int @@ -372,6 +416,9 @@ type StrictServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) interface{} + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + // (POST /text) TextExample(ctx context.Context, request TextExampleRequestObject) interface{} @@ -424,8 +471,8 @@ func (sh *strictHandler) JSONExample(ctx *gin.Context) { switch v := response.(type) { case JSONExample200JSONResponse: ctx.JSON(200, v) - case JSONExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case JSONExample400Response: + ctx.Status(400) case JSONExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -464,8 +511,8 @@ func (sh *strictHandler) MultipartExample(ctx *gin.Context) { if err := v(writer); err != nil { ctx.Error(err) } - case MultipartExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case MultipartExample400Response: + ctx.Status(400) case MultipartExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -553,8 +600,45 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *gin.Context) { } case MultipleRequestAndResponseTypes200TextResponse: ctx.Data(200, "text/plain", []byte(v)) - case MultipleRequestAndResponseTypes400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case MultipleRequestAndResponseTypes400Response: + ctx.Status(400) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx *gin.Context) { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.Bind(&body); err != nil { + ctx.Error(err) + return + } + request.Body = &body + + handler := func(ctx *gin.Context, request interface{}) interface{} { + return sh.ssi.ReusableResponses(ctx, request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response := handler(ctx, request) + + switch v := response.(type) { + case ReusableResponses200JSONResponse: + ctx.Header("header1", fmt.Sprint(v.Headers.Header1)) + ctx.Header("header2", fmt.Sprint(v.Headers.Header2)) + ctx.JSON(200, v) + case ReusableResponses400Response: + ctx.Status(400) + case ReusableResponsesdefaultResponse: + ctx.Status(v.StatusCode) case error: ctx.Error(v) case nil: @@ -587,8 +671,8 @@ func (sh *strictHandler) TextExample(ctx *gin.Context) { switch v := response.(type) { case TextExample200TextResponse: ctx.Data(200, "text/plain", []byte(v)) - case TextExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case TextExample400Response: + ctx.Status(400) case TextExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -620,8 +704,8 @@ func (sh *strictHandler) UnknownExample(ctx *gin.Context) { defer closer.Close() } ctx.DataFromReader(200, v.ContentLength, "video/mp4", v.Body, nil) - case UnknownExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case UnknownExample400Response: + ctx.Status(400) case UnknownExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -655,8 +739,8 @@ func (sh *strictHandler) UnspecifiedContentType(ctx *gin.Context) { defer closer.Close() } ctx.DataFromReader(200, v.ContentLength, v.ContentType, v.Body, nil) - case UnspecifiedContentType400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case UnspecifiedContentType400Response: + ctx.Status(400) case UnspecifiedContentTypedefaultResponse: ctx.Status(v.StatusCode) case error: @@ -698,8 +782,8 @@ func (sh *strictHandler) URLEncodedExample(ctx *gin.Context) { } else { ctx.Data(200, "application/x-www-form-urlencoded", []byte(form.Encode())) } - case URLEncodedExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case URLEncodedExample400Response: + ctx.Status(400) case URLEncodedExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -737,8 +821,8 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP ctx.Header("header1", fmt.Sprint(v.Headers.Header1)) ctx.Header("header2", fmt.Sprint(v.Headers.Header2)) ctx.JSON(200, v) - case HeadersExample400TextResponse: - ctx.Data(400, "text/plain", []byte(v)) + case HeadersExample400Response: + ctx.Status(400) case HeadersExampledefaultResponse: ctx.Status(v.StatusCode) case error: @@ -752,20 +836,21 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xXS28bNxD+KwO2p4DSOmlOujVGgL4D2MmpyGG0HGmZckmWnNVaMPa/F1yuZK+9MuRA", - "itGit31wvvn4zYu8FaWrvbNkOYrFrQgUvbOR+pclqkB/NxQ5vZXOMtn+kemGC29Q2/QWy4pqTE/fB1qJ", - "hfiuuAMt8t9Y3APruk4KRbEM2rN2VizEO1RX+79ygJwgwVtPYiEiB23XopOCbrD2htI/H5ynwDqT36Bp", - "aMKkk7svbvmFyoGNtiuXFo9ZXTrLqG0EpVcrCmQZBhUgYUSIjfcuMClYbiF5KBkihQ0FIQVrTsTE9f3v", - "MBCOQooNhZgdvZ5fzC/Sdpwni16Lhfih/ySFR676DRVfouv19i5rMeb6y/WHP0BHwIZdjaxLNGYLNYZY", - "oTGkQFt2iWNTcpyL3lXAZPyzGszfD1pKMSj+zqntg9Cj90aXvd2e0HEJsItU1ws+SrQ3FxfncPMwyT78", - "miR+m51NYexJjbI1waywMROif7J/WddaoBBcGHZW1I1h7THw/WCN1f59t+QYyfd4xcqFeqaQ8Uyqn8rT", - "iwo/NIPJIrmuXBuhci2wA0VooNVcwc7wQXVrCwhR27Uh2JGSk5E0NHSvH626GvbyMWGcvZbkCOVm1rbt", - "rA9eEwzZ0ilSXwera1xT4e16bJ6wkcVCLLec0vZxdz1REsmDU+ahy2/UTv5X+mSFncs1eT3cIj/SzVHd", - "8YRZ8q1leG5/a/LHw5oNVsfI9pVJd4SKG63IFbV/+0zkFxM1eir1SpOaDbuYZW6Hhsils2UgHk+LdPSy", - "jmEPlk6EXBFkBSREBy1B3UQGjzGC5jSDSqPzqVLRo9Hy6Y7ZZfaURsoRUX11ppi++pdEdNSPD1TK1W/v", - "85rnHnpP1vifObZO5/eFwpJOWrOKUFGIh4vrp7wASrSwTMeukvSGFKBVEIibYEnBRuPuJvOoagaAu7B6", - "DFgT917/vBWpuYtMQ0hhsab9++shCXRIynJoSD4xBeSTWG/EhK22TGtKinz+D9+xpLgX5Z2yTw5UuRdt", - "atmdat3Z87STIt/Tc7I0waSIMvtFUeT7/Ty2uF5TmGtXpJt697n7JwAA//9pypXBRREAAA==", + "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", + "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", + "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", + "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", + "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", + "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", + "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", + "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", + "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", + "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", + "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", + "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", + "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", + "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", + "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/examples/strict-server/gin/server.go b/internal/test/strict-server/gin/server.go similarity index 93% rename from examples/strict-server/gin/server.go rename to internal/test/strict-server/gin/server.go index 4252fbd3fd..876850a97c 100644 --- a/examples/strict-server/gin/server.go +++ b/internal/test/strict-server/gin/server.go @@ -73,7 +73,7 @@ func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, reque } }) default: - return MultipleRequestAndResponseTypes400TextResponse("content type is not supported") + return MultipleRequestAndResponseTypes400Response{} } } @@ -96,3 +96,7 @@ func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedE func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) interface{} { return HeadersExample200JSONResponse{Body: Example(*request.Body), Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}} } + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/examples/strict-server/gin/types.cfg.yaml b/internal/test/strict-server/gin/types.cfg.yaml similarity index 100% rename from examples/strict-server/gin/types.cfg.yaml rename to internal/test/strict-server/gin/types.cfg.yaml diff --git a/internal/test/strict-server/gin/types.gen.go b/internal/test/strict-server/gin/types.gen.go new file mode 100644 index 0000000000..33827cb7a4 --- /dev/null +++ b/internal/test/strict-server/gin/types.gen.go @@ -0,0 +1,54 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package api + +// Example defines model for example. +type Example struct { + Value *string `json:"value,omitempty"` +} + +// Reusableresponse defines model for reusableresponse. +type Reusableresponse = Example + +// MultipleRequestAndResponseTypesTextBody defines parameters for MultipleRequestAndResponseTypes. +type MultipleRequestAndResponseTypesTextBody = string + +// TextExampleTextBody defines parameters for TextExample. +type TextExampleTextBody = string + +// HeadersExampleParams defines parameters for HeadersExample. +type HeadersExampleParams struct { + Header1 string `json:"header1"` + Header2 *int `json:"header2,omitempty"` +} + +// JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. +type JSONExampleJSONRequestBody = Example + +// MultipartExampleMultipartRequestBody defines body for MultipartExample for multipart/form-data ContentType. +type MultipartExampleMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesJSONRequestBody defines body for MultipleRequestAndResponseTypes for application/json ContentType. +type MultipleRequestAndResponseTypesJSONRequestBody = Example + +// MultipleRequestAndResponseTypesFormdataRequestBody defines body for MultipleRequestAndResponseTypes for application/x-www-form-urlencoded ContentType. +type MultipleRequestAndResponseTypesFormdataRequestBody = Example + +// MultipleRequestAndResponseTypesMultipartRequestBody defines body for MultipleRequestAndResponseTypes for multipart/form-data ContentType. +type MultipleRequestAndResponseTypesMultipartRequestBody = Example + +// MultipleRequestAndResponseTypesTextRequestBody defines body for MultipleRequestAndResponseTypes for text/plain ContentType. +type MultipleRequestAndResponseTypesTextRequestBody = MultipleRequestAndResponseTypesTextBody + +// ReusableResponsesJSONRequestBody defines body for ReusableResponses for application/json ContentType. +type ReusableResponsesJSONRequestBody = Example + +// TextExampleTextRequestBody defines body for TextExample for text/plain ContentType. +type TextExampleTextRequestBody = TextExampleTextBody + +// URLEncodedExampleFormdataRequestBody defines body for URLEncodedExample for application/x-www-form-urlencoded ContentType. +type URLEncodedExampleFormdataRequestBody = Example + +// HeadersExampleJSONRequestBody defines body for HeadersExample for application/json ContentType. +type HeadersExampleJSONRequestBody = Example diff --git a/examples/strict-server/strict-schema.yaml b/internal/test/strict-server/strict-schema.yaml similarity index 88% rename from examples/strict-server/strict-schema.yaml rename to internal/test/strict-server/strict-schema.yaml index 6b48f99118..2b526fa53f 100644 --- a/examples/strict-server/strict-schema.yaml +++ b/internal/test/strict-server/strict-schema.yaml @@ -186,6 +186,22 @@ paths: $ref: "#/components/responses/badrequest" default: description: Unknown error + /reusable-responses: + post: + operationId: ReusableResponses + description: Responses can be refs to components/responses + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/example" + responses: + 200: + $ref: "#/components/responses/reusableresponse" + 400: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error /unspecified-content-type: post: operationId: UnspecifiedContentType @@ -212,16 +228,22 @@ components: responses: badrequest: description: BadRequest + reusableresponse: + description: OK + headers: + header1: + schema: + type: string + header2: + schema: + type: integer content: - text/plain: + application/json: schema: - $ref: "#/components/schemas/badrequest" - + $ref: "#/components/schemas/example" schemas: example: type: object properties: value: type: string - badrequest: - type: string diff --git a/examples/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go similarity index 82% rename from examples/strict-server/strict_test.go rename to internal/test/strict-server/strict_test.go index e7766197d4..187ca7846c 100644 --- a/examples/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -16,54 +16,54 @@ import ( "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - chiapi "github.com/deepmap/oapi-codegen/examples/strict-server/chi" - client "github.com/deepmap/oapi-codegen/examples/strict-server/client" - echoapi "github.com/deepmap/oapi-codegen/examples/strict-server/echo" - ginapi "github.com/deepmap/oapi-codegen/examples/strict-server/gin" + "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" + api3 "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" + api4 "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" + api2 "github.com/deepmap/oapi-codegen/internal/test/strict-server/gin" "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/deepmap/oapi-codegen/pkg/testutil" ) func TestChiServer(t *testing.T) { - server := chiapi.StrictServer{} - strictHandler := chiapi.NewStrictHandler(server, nil) + server := api.StrictServer{} + strictHandler := api.NewStrictHandler(server, nil) r := chi.NewRouter() - handler := chiapi.HandlerFromMux(strictHandler, r) + handler := api.HandlerFromMux(strictHandler, r) testImpl(t, handler) } func TestEchoServer(t *testing.T) { - server := echoapi.StrictServer{} - strictHandler := echoapi.NewStrictHandler(server, nil) + server := api4.StrictServer{} + strictHandler := api4.NewStrictHandler(server, nil) e := echo.New() - echoapi.RegisterHandlers(e, strictHandler) + api4.RegisterHandlers(e, strictHandler) testImpl(t, e) } func TestGinServer(t *testing.T) { - server := ginapi.StrictServer{} - strictHandler := ginapi.NewStrictHandler(server, nil) + server := api2.StrictServer{} + strictHandler := api2.NewStrictHandler(server, nil) gin.SetMode(gin.ReleaseMode) r := gin.New() - handler := ginapi.RegisterHandlers(r, strictHandler) + handler := api2.RegisterHandlers(r, strictHandler) testImpl(t, handler) } func testImpl(t *testing.T, handler http.Handler) { t.Run("JSONExample", func(t *testing.T) { value := "123" - requestBody := client.Example{Value: &value} + requestBody := api3.Example{Value: &value} rr := testutil.NewRequest().Post("/json").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody client.Example + var responseBody api3.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) }) t.Run("URLEncodedExample", func(t *testing.T) { value := "456" - requestBody := client.Example{Value: &value} + requestBody := api3.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -71,7 +71,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody client.Example + var responseBody api3.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -116,18 +116,18 @@ func testImpl(t *testing.T, handler http.Handler) { }) t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { value := "123" - requestBody := client.Example{Value: &value} + requestBody := api3.Example{Value: &value} rr := testutil.NewRequest().Post("/multiple").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody client.Example + var responseBody api3.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) }) t.Run("MultipleRequestAndResponseTypesFormdata", func(t *testing.T) { value := "456" - requestBody := client.Example{Value: &value} + requestBody := api3.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -135,7 +135,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody client.Example + var responseBody api3.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -182,11 +182,11 @@ func testImpl(t *testing.T, handler http.Handler) { header1 := "value1" header2 := "890" value := "asdf" - requestBody := client.Example{Value: &value} + requestBody := api3.Example{Value: &value} rr := testutil.NewRequest().Post("/with-headers").WithHeader("header1", header1).WithHeader("header2", header2).WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder assert.Equal(t, http.StatusOK, rr.Code) assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) - var responseBody client.Example + var responseBody api3.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -201,4 +201,15 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, contentType, rr.Header().Get("Content-Type")) assert.Equal(t, data, rr.Body.Bytes()) }) + t.Run("ReusableResponses", func(t *testing.T) { + value := "jkl;" + requestBody := api3.Example{Value: &value} + rr := testutil.NewRequest().Post("/reusable-responses").WithJsonBody(requestBody).GoWithHTTPHandler(t, handler).Recorder + assert.Equal(t, http.StatusOK, rr.Code) + assert.True(t, strings.HasPrefix(rr.Header().Get("Content-Type"), "application/json")) + var responseBody api3.Example + err := json.NewDecoder(rr.Body).Decode(&responseBody) + assert.NoError(t, err) + assert.Equal(t, requestBody, responseBody) + }) } diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index e1836ec927..13f76a28ed 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -178,10 +178,19 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { var strictServerOut string if opts.Generate.Strict { + responses, err := GenerateResponseDefinitions("", spec.Components.Responses) + if err != nil { + return "", fmt.Errorf("error generation response definitions for schema: %w", err) + } + strictServerResponses, err := GenerateStrictResponses(t, responses) + if err != nil { + return "", fmt.Errorf("error generation response definitions for schema: %w", err) + } strictServerOut, err = GenerateStrictServer(t, ops, opts) if err != nil { return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) } + strictServerOut = strictServerResponses + strictServerOut } var clientOut string diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index f8cc4248d4..78123d88e4 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -408,6 +408,7 @@ type ResponseDefinition struct { Description string Contents []ResponseContentDefinition Headers []ResponseHeaderDefinition + Ref string } func (r ResponseDefinition) HasFixedStatusCode() bool { @@ -415,6 +416,14 @@ func (r ResponseDefinition) HasFixedStatusCode() bool { return err == nil } +func (r ResponseDefinition) GoName() string { + return SchemaNameToTypeName(r.StatusCode) +} + +func (r ResponseDefinition) IsRef() bool { + return r.Ref != "" +} + type ResponseContentDefinition struct { // This is the schema describing this content Schema Schema @@ -430,7 +439,7 @@ type ResponseContentDefinition struct { // TypeDef returns the Go type definition for a request body func (r ResponseContentDefinition) TypeDef(opID string, statusCode int) *TypeDefinition { return &TypeDefinition{ - TypeName: fmt.Sprintf("%s%v%sResponse", opID, statusCode, r.NameTag), + TypeName: fmt.Sprintf("%s%v%sResponse", opID, statusCode, r.NameTagOrContentType()), Schema: r.Schema, } } @@ -720,16 +729,6 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response return nil, fmt.Errorf("error generating request body definition: %w", err) } - // If the response is a pre-defined type - if IsGoTypeReference(responseOrRef.Ref) { - // Convert the reference path to Go type - refType, err := RefPathToGoType(responseOrRef.Ref) - if err != nil { - return nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", responseOrRef.Ref, err) - } - contentSchema.RefType = refType - } - rcd := ResponseContentDefinition{ ContentType: contentType, NameTag: tag, @@ -757,6 +756,14 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response if response.Description != nil { rd.Description = *response.Description } + if IsGoTypeReference(responseOrRef.Ref) { + // Convert the reference path to Go type + refType, err := RefPathToGoType(responseOrRef.Ref) + if err != nil { + return nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", responseOrRef.Ref, err) + } + rd.Ref = refType + } responseDefinitions = append(responseDefinitions, rd) } @@ -897,6 +904,10 @@ func GenerateStrictServer(t *template.Template, operations []OperationDefinition return GenerateTemplates(templates, t, operations) } +func GenerateStrictResponses(t *template.Template, responses []ResponseDefinition) (string, error) { + return GenerateTemplates([]string{"strict/strict-responses.tmpl"}, t, responses) +} + // Uses the template engine to generate the function which registers our wrappers // as Echo path handlers. func GenerateClient(t *template.Template, ops []OperationDefinition) (string, error) { diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index b71656d76d..91994d547b 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -17,11 +17,13 @@ } {{range .Responses}} - {{$statusCode := .StatusCode}} - {{$hasHeaders := ne 0 (len .Headers)}} - {{$fixedStatusCode := .HasFixedStatusCode}} + {{$statusCode := .StatusCode -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$isRef := .IsRef -}} + {{$ref := .Ref | ucFirst -}} - {{if $hasHeaders}} + {{if (and $hasHeaders (not $isRef)) -}} type {{$opid}}{{$statusCode}}ResponseHeaders struct { {{range .Headers -}} {{.GoName}} {{.Schema.TypeDecl}} @@ -30,11 +32,13 @@ {{end}} {{range .Contents}} - {{if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} - type {{$opid}}{{$statusCode}}{{.NameTag}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + {{if and $fixedStatusCode $isRef -}} + type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response = {{$ref}}{{.NameTagOrContentType}}Response + {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} - {{if and (not (and .Schema.IsRef)) (eq .NameTag "JSON")}} - func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + {{if and (not .Schema.IsRef) (eq .NameTag "JSON")}} + func (t {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { return json.Marshal(({{.Schema.GoType}})(t)) } {{end}} @@ -42,7 +46,7 @@ type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response struct { Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} - Headers {{$opid}}{{$statusCode}}ResponseHeaders + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders {{end -}} {{if not $fixedStatusCode -}} @@ -58,7 +62,7 @@ {{end -}} } {{if eq .NameTag "JSON"}} - func (t {{$opid}}{{$statusCode}}{{.NameTag}}Response) MarshalJSON() ([]byte, error) { + func (t {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { return json.Marshal(t.Body) } {{end}} @@ -66,14 +70,18 @@ {{end}} {{if eq 0 (len .Contents) -}} - type {{$opid}}{{$statusCode}}Response struct { - {{if $hasHeaders -}} - Headers {{$opid}}{{$statusCode}}ResponseHeaders - {{end}} - {{if not $fixedStatusCode -}} - StatusCode int - {{end -}} - } + {{if and $fixedStatusCode $isRef -}} + type {{$opid}}{{$statusCode}}Response = {{$ref}}Response + {{else -}} + type {{$opid}}{{$statusCode}}Response struct { + {{if $hasHeaders -}} + Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end}} + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + } + {{end -}} {{end}} {{end}} {{end}} diff --git a/pkg/codegen/templates/strict/strict-responses.tmpl b/pkg/codegen/templates/strict/strict-responses.tmpl new file mode 100644 index 0000000000..6034d8fc6d --- /dev/null +++ b/pkg/codegen/templates/strict/strict-responses.tmpl @@ -0,0 +1,52 @@ +{{range . -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$name := .GoName | ucFirst -}} + {{if $hasHeaders -}} + type {{$name}}ResponseHeaders struct { + {{range .Headers -}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end -}} + } + {{end -}} + + {{range .Contents -}} + {{if and (not $hasHeaders) (.IsSupported) -}} + type {{$name}}{{.NameTagOrContentType}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + + {{if and (not (and .Schema.IsRef)) (eq .NameTag "JSON")}} + func (t {{$name}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(({{.Schema.GoType}})(t)) + } + {{end}} + {{else -}} + type {{$name}}{{.NameTagOrContentType}}Response struct { + Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + + {{if $hasHeaders -}} + Headers {{$name}}ResponseHeaders + {{end -}} + + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} + } + {{if eq .NameTag "JSON"}} + func (t {{$name}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} + {{end -}} + {{end -}} + + {{if eq 0 (len .Contents) -}} + type {{$name}}Response struct { + {{if $hasHeaders -}} + Headers {{$name}}ResponseHeaders + {{end}} + } + {{end}} +{{end -}} \ No newline at end of file From d814010542894c79ea288f74b217f3a009d7feba Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 22 May 2022 22:28:09 +0300 Subject: [PATCH 16/19] When multiple responses ref to a single reusable response, only the first one will use an alias, all others will generate new structs --- internal/test/strict-server/chi/server.gen.go | 40 ++++++++++++------- .../test/strict-server/echo/server.gen.go | 40 ++++++++++++------- internal/test/strict-server/gin/server.gen.go | 40 ++++++++++++------- .../test/strict-server/strict-schema.yaml | 4 ++ pkg/codegen/operations.go | 10 ++++- 5 files changed, 88 insertions(+), 46 deletions(-) diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go index 43a84f88b4..3a7382e897 100644 --- a/internal/test/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -515,6 +515,12 @@ type UnspecifiedContentType200VideoResponse struct { type UnspecifiedContentType400Response = BadrequestResponse +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + type UnspecifiedContentTypedefaultResponse struct { StatusCode int } @@ -922,6 +928,10 @@ func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.R _, _ = io.Copy(w, v.Body) case UnspecifiedContentType400Response: w.WriteHeader(400) + case UnspecifiedContentType401Response: + w.WriteHeader(401) + case UnspecifiedContentType403Response: + w.WriteHeader(403) case UnspecifiedContentTypedefaultResponse: w.WriteHeader(v.StatusCode) case error: @@ -1033,21 +1043,21 @@ func writeRaw(w http.ResponseWriter, b []byte) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", - "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", - "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", - "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", - "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", - "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", - "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", - "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", - "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", - "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", - "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", - "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", - "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", - "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", - "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", + "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", + "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", + "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", + "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", + "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", + "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", + "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", + "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", + "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", + "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", + "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", + "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", + "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", + "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", + "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go index 32eef904eb..9ba52dca69 100644 --- a/internal/test/strict-server/echo/server.gen.go +++ b/internal/test/strict-server/echo/server.gen.go @@ -341,6 +341,12 @@ type UnspecifiedContentType200VideoResponse struct { type UnspecifiedContentType400Response = BadrequestResponse +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + type UnspecifiedContentTypedefaultResponse struct { StatusCode int } @@ -726,6 +732,10 @@ func (sh *strictHandler) UnspecifiedContentType(ctx echo.Context) error { return ctx.Stream(200, v.ContentType, v.Body) case UnspecifiedContentType400Response: return ctx.NoContent(400) + case UnspecifiedContentType401Response: + return ctx.NoContent(401) + case UnspecifiedContentType403Response: + return ctx.NoContent(403) case UnspecifiedContentTypedefaultResponse: return ctx.NoContent(v.StatusCode) case error: @@ -822,21 +832,21 @@ func (sh *strictHandler) HeadersExample(ctx echo.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", - "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", - "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", - "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", - "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", - "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", - "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", - "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", - "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", - "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", - "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", - "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", - "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", - "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", - "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", + "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", + "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", + "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", + "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", + "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", + "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", + "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", + "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", + "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", + "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", + "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", + "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", + "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", + "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", + "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go index 939295211e..56e75529ab 100644 --- a/internal/test/strict-server/gin/server.gen.go +++ b/internal/test/strict-server/gin/server.gen.go @@ -363,6 +363,12 @@ type UnspecifiedContentType200VideoResponse struct { type UnspecifiedContentType400Response = BadrequestResponse +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + type UnspecifiedContentTypedefaultResponse struct { StatusCode int } @@ -741,6 +747,10 @@ func (sh *strictHandler) UnspecifiedContentType(ctx *gin.Context) { ctx.DataFromReader(200, v.ContentLength, v.ContentType, v.Body, nil) case UnspecifiedContentType400Response: ctx.Status(400) + case UnspecifiedContentType401Response: + ctx.Status(401) + case UnspecifiedContentType403Response: + ctx.Status(403) case UnspecifiedContentTypedefaultResponse: ctx.Status(v.StatusCode) case error: @@ -836,21 +846,21 @@ func (sh *strictHandler) HeadersExample(ctx *gin.Context, params HeadersExampleP // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYzY4iNxB+FauS06qhZyZ74pYdrZT/lZjdU7SHol2AN27bsavpQYh3j9w2DCw9iFnB", - "oES50ab+/FV9VbZXUNnaWUOGA4xW4Ck4awJ1HxOUnv5uKHD8khQqrxwra2AE71CO83/rAjw1ASeaNupR", - "vrKGyXSq6JxWFUbV8kuI+isI1ZxqjL++9zSFEXxXPoVSpn9DSY9YO02wXq+LryL48CsUMCeU5Lto08/b", - "fdu8dAQjCOyVmUE0ksTuesWUYZqRj96iaA4iCmziGK3AeevIs0oYLVA31O8pr9jJF6o47UCZqT3E8t4a", - "RmWCkGo6JU+GRQZPRBtBhMY565mkmCxF9FCxCOQX5KEAVhwDg4fddZEDDlDAgnxIjm6HN8ObmC/ryKBT", - "MIIfuqUCHPK829A2Qc725f2Xhw9/CBUENmxrZFWh1ktRow9z1JqkUIZtjLGpOAyhc+W7zP8ss/r7jGUs", - "m66C3lm5vETFdIW5U893NzevVJjrAt4mZ302tkGVOwzrzEyx0T2gfzJ/GdsaQd5bn3dW1o1m5dDzbrL2", - "0f59I3IK5Ft75dT6eiCR8UKon8vTVYHPzaCXJA9z2wYxt61gKyShFq3iudgofsVuZQSKoMxMk9gEVfRm", - "UlPuuT8aOc57+RhtXJxLxZ6Vx0HbtoMueY3XZCorSX6bWVXjjEpnZvvq0TYyjGCy5Fi2h931TEVUANMj", - "l06jMsdHxyu1k/+RPhuxE103Z5PBXvL6ibshVRAVGjGJfJyGSOI+XwckHWdP4x2J64y44xgdnNZeo2vG", - "5D8/qT7S40lD6oxkfe1qfClgTVp8HrOsdQps38j9E1BcKEm2rN3bF1q+GqjBUaWmiuQg72KQYnuuJdxb", - "U3ni/aEdT8DGstgaiwdznpNICBQiWNGSqJvAwmEIQnHXRbRKh3tJB83j01Nk98lTnOwnZPXNhXL65l+S", - "0b2x+AxTxr+9TzIvvXucbf6+8PRwPr9XSks88A52buf95PopCTxN24rUgqRAI4UnbrwhKRYKNxfKA9Zk", - "A09pdeixJu68/rmC2NzzlR8KMFjT9vs2F4HyEVn2DRXHHg6O2rqD4thrwuf/8FX3km8wl67TdQHpuSQV", - "S+N1zCizG5VlemYZhhZnM/JDZUt0Ctaf1/8EAAD//33ZZK8zEwAA", + "H4sIAAAAAAAC/+xYS4/iOBD+K1btnkaB0D194rbTGmnfI9Ezp9UcirgAzya2166QRoj/vnJsaBjSCFo8", + "pNXeEqde/qq+KsdLKExljSbNHoZLcOSt0Z7alzFKR//U5Dm8SfKFU5aV0TCEDyhH6dsqA0e1x3FJa/Ug", + "XxjNpFtVtLZUBQbV/JsP+kvwxYwqDE8/OprAEH7IX0LJ41ef0zNWtiRYrVbZdxF8+g0ymBFKcm208fFu", + "1zYvLMEQPDulpxCMRLH7TjGlmabkgrcgmoIIAus4hkuwzlhyrCJGcyxr6vaUVsz4GxUcd6D0xOxj+Wg0", + "o9JeSDWZkCPNIoEngg0vfG2tcUxSjBcieChYeHJzcpABKw6BwdP2ukgBe8hgTs5HR3f9QX8Q8mUsabQK", + "hvC+XcrAIs/aDW0SZE1X3n99+vSnUF5gzaZCVgWW5UJU6PwMy5KkUJpNiLEu2PehdeXazP8ik/rHhGUo", + "m7aCPhi5uETFtIW5Vc/3g8GVCnOVwUN01mVjE1S+xbDWzATrsgP0L/pvbRotyDnj0s7yqi5ZWXS8naxd", + "tP9YixwD+cZePjGu6klkvBDq5/J0U+BTM+gkydPMNF7MTCPYCElYikbxTKwVv2O30gKFV3paklgHlXVm", + "sqTUc3/ScpT28jnYuDiXsh0rz72maXpt8mpXki6MJPk2s6rCKeVWT3fVg21kGMJ4waFs97vrmYooA6Zn", + "zm2JSh8eHVdqJ/8jfTZiR7quzya9neR1E3dNKi8K1GIc+DjxgcRdvvZIOkqeRlsStxlxhzHaO61do2uG", + "5L8+qT7T81FD6oxkvXY1ngpYHRdfxyxpHQPbG7l/BIpzJcnklX040fLNQPWWCjVRJHtpF70Y22st4dHo", + "whHvDu1wAtaGxcZYOJjzjEREIBPeiIZEVXsWFr0XitsuUqp4uJe01zy+vET2GD2FyX5EVt9dKKfvbpXR", + "h8Hd6SrvL1w3O8P3FT6Ofv8YZU79wznblD/xjHI+vzeiczhW97buALop/HMUeJnpBak5SYFaCkdcO01S", + "zBWuf1v3uJkMvKTVosOKuPX61xLCCEkXC5CBxoo273epCJQLyLKrKTt0PXHQ1j1kh+4svv6Hf6gvedNz", + "6TpdZRAvZWKx1K4MGWW2wzyPlzl93+B0Sq6vTI5Wwerr6t8AAAD//ygSomqZEwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/test/strict-server/strict-schema.yaml b/internal/test/strict-server/strict-schema.yaml index 2b526fa53f..a9a2c41097 100644 --- a/internal/test/strict-server/strict-schema.yaml +++ b/internal/test/strict-server/strict-schema.yaml @@ -222,6 +222,10 @@ paths: format: byte 400: $ref: "#/components/responses/badrequest" + 401: + $ref: "#/components/responses/badrequest" + 403: + $ref: "#/components/responses/badrequest" default: description: Unknown error components: diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 78123d88e4..8ea1b957ba 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -693,6 +693,8 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody func GenerateResponseDefinitions(operationID string, responses openapi3.Responses) ([]ResponseDefinition, error) { var responseDefinitions []ResponseDefinition + // do not let multiple status codes ref to same response, it will break the type switch + refSet := make(map[string]struct{}) for _, statusCode := range SortedResponsesKeys(responses) { responseOrRef := responses[statusCode] @@ -762,7 +764,13 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response if err != nil { return nil, fmt.Errorf("error turning reference (%s) into a Go type: %w", responseOrRef.Ref, err) } - rd.Ref = refType + // Check if this ref is already used by another response definition. If not use the ref + // If we let multiple response definitions alias to same response it will break the type switch + // so only the first response will use the ref, other will generate new structs + if _, ok := refSet[refType]; !ok { + rd.Ref = refType + refSet[refType] = struct{}{} + } } responseDefinitions = append(responseDefinitions, rd) } From c8d951f5257779198dfe9f934b16260817abff10 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Wed, 1 Jun 2022 11:10:51 +0300 Subject: [PATCH 17/19] Update generated code after merge --- .../gorilla/api/petstore.gen.go | 5 +- .../strict/api/petstore-server.gen.go | 26 ++++----- internal/test/strict-server/chi/server.gen.go | 56 +++++++++---------- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/examples/petstore-expanded/gorilla/api/petstore.gen.go b/examples/petstore-expanded/gorilla/api/petstore.gen.go index 5ddb9e1a6f..5a3e901245 100644 --- a/examples/petstore-expanded/gorilla/api/petstore.gen.go +++ b/examples/petstore-expanded/gorilla/api/petstore.gen.go @@ -57,11 +57,8 @@ type FindPetsParams struct { Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` } -// AddPetJSONBody defines parameters for AddPet. -type AddPetJSONBody = NewPet - // AddPetJSONRequestBody defines body for AddPet for application/json ContentType. -type AddPetJSONRequestBody = AddPetJSONBody +type AddPetJSONRequestBody = NewPet // ServerInterface represents all server handlers. type ServerInterface interface { diff --git a/examples/petstore-expanded/strict/api/petstore-server.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go index 016667a730..7c10ee2370 100644 --- a/examples/petstore-expanded/strict/api/petstore-server.gen.go +++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go @@ -43,7 +43,7 @@ type ServerInterfaceWrapper struct { ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) } -type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc +type MiddlewareFunc func(http.Handler) http.Handler // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { @@ -76,30 +76,30 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque return } - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPets(w, r, params) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AddPet(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // DeletePet operation middleware @@ -117,15 +117,15 @@ func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Requ return } - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeletePet(w, r, id) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // FindPetByID operation middleware @@ -143,15 +143,15 @@ func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Re return } - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.FindPetByID(w, r, id) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } type UnescapedCookieParamError struct { diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go index 3a7382e897..32f2692d93 100644 --- a/internal/test/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -61,126 +61,126 @@ type ServerInterfaceWrapper struct { ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) } -type MiddlewareFunc func(http.HandlerFunc) http.HandlerFunc +type MiddlewareFunc func(http.Handler) http.Handler // JSONExample operation middleware func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.JSONExample(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // MultipartExample operation middleware func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipartExample(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // MultipleRequestAndResponseTypes operation middleware func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipleRequestAndResponseTypes(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // ReusableResponses operation middleware func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ReusableResponses(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // TextExample operation middleware func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.TextExample(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // UnknownExample operation middleware func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnknownExample(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // UnspecifiedContentType operation middleware func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnspecifiedContentType(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // URLEncodedExample operation middleware func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.URLEncodedExample(w, r) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } // HeadersExample operation middleware @@ -236,15 +236,15 @@ func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http } - var handler = func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.HeadersExample(w, r, params) - } + }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler(w, r.WithContext(ctx)) + handler.ServeHTTP(w, r.WithContext(ctx)) } type UnescapedCookieParamError struct { From 825c4dbcc5f289ca9c09b832baa395cb1048ae86 Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Sun, 19 Jun 2022 22:52:00 +0300 Subject: [PATCH 18/19] Some documentation for strict server --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 5cc41ccd46..07224f8c58 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,47 @@ Alternatively, [Gorilla](https://github.com/gorilla/mux) is also 100% compatible +#### Strict server generation + +oapi-codegen also supports generating RPC inspired strict server, that will parse request bodies and encode responses. +The main points of this code is to automate some parsing, abstract user code from server specific code, +and also to force user code to comply with the schema. +It supports binding of `application/json` and `application/x-www-form-urlencoded` to a struct, for `multipart` requests +it generates a `multipart.Reader`, which can be used to either manually iterating over parts or using `runtime.BindMultipart` +function to bind the form to a struct. All other content types are represented by a `io.Reader` interface. + +To form a response simply return one of the generated structs with corresponding status code and content type. For example, +to return a status code 200 JSON response for a AddPet use the `AddPet200JSONResponse` struct which will set the correct +Content-Type header, status code and will marshal the response data. You can also return an `error` interface, that will be +cause an `Internal Server Error` response. If you return a response that is not supported by this method, you will get an error. +Unfortunately go does not support union types outside generic code, so we can't type check in compile time. + +Short example: +```go +type PetStoreImpl struct {} +func (*PetStoreImpl) GetPets(ctx context.Context, request GetPetsRequestObject) interface{} { + var result []Pet + // Implement me + return GetPets200JSONResponse(result) +} +``` +For a complete example see `/examples/petstore-expanded/strict`. + +Code is generation with a configuration flag `genrate: strict-server: true` along with any other server (echo, chi, gin and gorilla are supported). +The generated strict wrapper can then be used as an implementation for `ServerInterface`. Setup example: +```go +func SetupHandler() { + var myApi PetStoreImpl + myStrictApiHandler := api.NewStrictHandler(myApi, nil) + e := echo.New() + petstore.RegisterHandlers(e, &myStrictApiHandler) +} +``` + +Strict server also has its own middlewares. It can access to both request and response structs, +as well as raw request\response data. It can be used for logging the parsed request\response objects, transforming go errors into response structs, +authorization, etc. Note that middlewares are server-specific. + #### Additional Properties in type definitions [OpenAPI Schemas](https://swagger.io/specification/#schemaObject) implicitly From 1a7338bb51dedf0bdce5ba2a6c61c273bc47d6fd Mon Sep 17 00:00:00 2001 From: "ilya.bogdanov" Date: Tue, 21 Jun 2022 16:06:12 +0300 Subject: [PATCH 19/19] Support for AdditionalProperties when binding forms --- pkg/runtime/bindform.go | 69 +++++++++++++++++++++++++++++++++--- pkg/runtime/bindform_test.go | 8 +++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go index af4f912042..b502f55af1 100644 --- a/pkg/runtime/bindform.go +++ b/pkg/runtime/bindform.go @@ -22,6 +22,15 @@ type RequestBodyEncoding struct { Explode *bool } +func BindMultipart(ptr interface{}, reader multipart.Reader) error { + const defaultMemory = 32 << 20 + form, err := reader.ReadForm(defaultMemory) + if err != nil { + return err + } + return BindForm(ptr, form.Value, form.File, nil) +} + func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]RequestBodyEncoding) error { ptrVal := reflect.Indirect(reflect.ValueOf(ptr)) if ptrVal.Kind() != reflect.Struct { @@ -158,8 +167,8 @@ func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][] for i := 0; i < v.NumField(); i++ { field := v.Type().Field(i) tag := field.Tag.Get(tagName) - if field.Name == "AdditionalProperties" && tag == "-" { - additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), form, files, name) + if field.Name == "AdditionalProperties" && field.Type.Kind() == reflect.Map && tag == "-" { + additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), v, form, files, name) if err != nil { return false, err } @@ -213,9 +222,59 @@ func indexedElementsCount(form map[string][]string, files map[string][]*multipar return maxIndex + 1 } -func bindAdditionalProperties(additionalProperties reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { - // TODO: support additional properties - return false, nil +func bindAdditionalProperties(additionalProperties reflect.Value, parentStruct reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) { + hasData := false + valueType := additionalProperties.Type().Elem() + + // store all fixed properties in a set + fieldsSet := make(map[string]struct{}) + for i := 0; i < parentStruct.NumField(); i++ { + tag := parentStruct.Type().Field(i).Tag.Get(tagName) + if !parentStruct.Field(i).CanInterface() || tag == "-" { + continue + } + tag = strings.Split(tag, ",")[0] + fieldsSet[tag] = struct{}{} + } + + result := reflect.MakeMap(additionalProperties.Type()) + for k := range form { + if strings.HasPrefix(k, name+"[") { + key := strings.TrimPrefix(k, name+"[") + key = key[:strings.Index(key, "]")] + if _, ok := fieldsSet[key]; ok { + continue + } + value := reflect.New(valueType) + ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key)) + if err != nil { + return false, err + } + result.SetMapIndex(reflect.ValueOf(key), value.Elem()) + hasData = hasData || ptrHasData + } + } + for k := range files { + if strings.HasPrefix(k, name+"[") { + key := strings.TrimPrefix(k, name+"[") + key = key[:strings.Index(key, "]")] + if _, ok := fieldsSet[key]; ok { + continue + } + value := reflect.New(valueType) + result.SetMapIndex(reflect.ValueOf(key), value) + ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key)) + if err != nil { + return false, err + } + result.SetMapIndex(reflect.ValueOf(key), value.Elem()) + hasData = hasData || ptrHasData + } + } + if hasData { + additionalProperties.Set(result) + } + return hasData, nil } func marshalFormImpl(v reflect.Value, result url.Values, name string) { diff --git a/pkg/runtime/bindform_test.go b/pkg/runtime/bindform_test.go index 97a6af608d..ac47ef2c98 100644 --- a/pkg/runtime/bindform_test.go +++ b/pkg/runtime/bindform_test.go @@ -13,8 +13,9 @@ import ( func TestBindURLForm(t *testing.T) { type testSubStruct struct { - Int int `json:"int"` - String string `json:"string"` + Int int `json:"int"` + String string `json:"string"` + AdditionalProperties map[string]string `json:"-"` } type testStruct struct { Int int `json:"int"` @@ -50,6 +51,9 @@ func TestBindURLForm(t *testing.T) { "opt_struct_slice[0][int]=123&opt_struct_slice[0][string]=abc&opt_struct_slice[1][int]=456&opt_struct_slice[1][string]=def": { OptStructSlice: &([]testSubStruct{{Int: 123, String: "abc"}, {Int: 456, String: "def"}}), }, + "opt_struct[additional_property]=123": { + OptStruct: &testSubStruct{AdditionalProperties: map[string]string{"additional_property": "123"}}, + }, } for k, v := range testCases {