diff --git a/README.md b/README.md index 5cc41ccd4..07224f8c5 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 diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index 2d5c0f990..5ad3d3208 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -323,6 +323,8 @@ func newConfigFromOldConfig(c oldConfiguration) configuration { opts.Generate.GinServer = true case "gorilla": opts.Generate.GorillaServer = 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 45bc23554..14f393d19 100644 --- a/examples/authenticated-api/echo/api/api.gen.go +++ b/examples/authenticated-api/echo/api/api.gen.go @@ -45,11 +45,8 @@ type ThingWithID struct { Name string `json:"name"` } -// 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 64aec35cb..4a514dc23 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/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/echo/api/models/models.gen.go b/examples/petstore-expanded/echo/api/models/models.gen.go index db163fd64..701ac37f2 100644 --- a/examples/petstore-expanded/echo/api/models/models.gen.go +++ b/examples/petstore-expanded/echo/api/models/models.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/gin/api/petstore-types.gen.go b/examples/petstore-expanded/gin/api/petstore-types.gen.go index 67ee510b5..e3c73d101 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/gorilla/api/petstore.gen.go b/examples/petstore-expanded/gorilla/api/petstore.gen.go index 5ddb9e1a6..5a3e90124 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/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go index 6a73e9b2e..bbd8aa2e6 100644 --- a/examples/petstore-expanded/petstore-client.gen.go +++ b/examples/petstore-expanded/petstore-client.gen.go @@ -56,11 +56,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 // 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-server.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go new file mode 100644 index 000000000..7c10ee237 --- /dev/null +++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go @@ -0,0 +1,634 @@ +// 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" +) + +// 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.Handler) http.Handler + +// 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 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// AddPet operation middleware +func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(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.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + 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.ServeHTTP(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.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, chi.URLParam(r, "id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + 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.ServeHTTP(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-types.gen.go b/examples/petstore-expanded/strict/api/petstore-types.gen.go new file mode 100644 index 000000000..e3c73d101 --- /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 new file mode 100644 index 000000000..316425ab3 --- /dev/null +++ b/examples/petstore-expanded/strict/api/petstore.go @@ -0,0 +1,105 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "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/api/server.cfg.yaml b/examples/petstore-expanded/strict/api/server.cfg.yaml new file mode 100644 index 000000000..f3c71c63f --- /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 000000000..9ac30e11e --- /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.go b/examples/petstore-expanded/strict/petstore.go new file mode 100644 index 000000000..4e33e9e19 --- /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 000000000..67bbb1ac0 --- /dev/null +++ b/examples/petstore-expanded/strict/petstore_test.go @@ -0,0 +1,168 @@ +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: { + Tag: &tag, + }, + 2: {}, + } + + // 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/internal/test/client/client.gen.go b/internal/test/client/client.gen.go index 2319a55fb..d545ec38e 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 22cc63b95..37b0cefbe 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -349,6 +349,14 @@ type RenamedRequestBody 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 defines parameters for ParamsWithAddProps. type ParamsWithAddPropsParams struct { // This parameter has additional properties @@ -369,7 +377,10 @@ type BodyWithAddPropsJSONBody struct { } // EnsureEverythingIsReferencedJSONRequestBody defines body for EnsureEverythingIsReferenced for application/json ContentType. -type EnsureEverythingIsReferencedJSONRequestBody RenamedRequestBody +type EnsureEverythingIsReferencedJSONRequestBody EnsureEverythingIsReferencedJSONBody + +// EnsureEverythingIsReferencedTextRequestBody defines body for EnsureEverythingIsReferenced for text/plain ContentType. +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 480b03d3a..3f4c9183b 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 e65ba867d..c80306102 100644 --- a/internal/test/schemas/schemas.gen.go +++ b/internal/test/schemas/schemas.gen.go @@ -71,9 +71,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{} @@ -83,7 +80,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 97d86d43d..bd747ca19 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -106,12 +106,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 @@ -125,10 +119,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/internal/test/strict-server/chi/server.cfg.yaml b/internal/test/strict-server/chi/server.cfg.yaml new file mode 100644 index 000000000..ca1c62c3c --- /dev/null +++ b/internal/test/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/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go new file mode 100644 index 000000000..32f2692d9 --- /dev/null +++ b/internal/test/strict-server/chi/server.gen.go @@ -0,0 +1,1133 @@ +// 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" +) + +// 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 /reusable-responses) + ReusableResponses(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 /unspecified-content-type) + UnspecifiedContentType(w http.ResponseWriter, r *http.Request) + + // (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. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +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 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(w, r.WithContext(ctx)) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + 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.ServeHTTP(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 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.ServeHTTP(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+"/reusable-responses", wrapper.ReusableResponses) + }) + 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+"/unspecified-content-type", wrapper.UnspecifiedContentType) + }) + 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 +} + +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 +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400Response = BadrequestResponse + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +type MultipartExample400Response = BadrequestResponse + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +type MultipleRequestAndResponseTypes200TextResponse string + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExample200TextResponse string + +type TextExample400Response = BadrequestResponse + +type TextExampledefaultResponse struct { + StatusCode int +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type UnknownExample400Response = BadrequestResponse + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400Response = BadrequestResponse + +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExample200FormdataResponse Example + +type URLEncodedExample400Response = BadrequestResponse + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400Response = BadrequestResponse + +type HeadersExampledefaultResponse 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 /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) 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{} + + // (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{} + +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 JSONExample400Response: + w.WriteHeader(400) + 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 MultipartExample200MultipartResponse: + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + defer writer.Close() + if err := v(writer); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + case MultipartExample400Response: + w.WriteHeader(400) + 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 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 MultipleRequestAndResponseTypes200MultipartResponse: + writer := multipart.NewWriter(w) + w.Header().Set("Content-Type", writer.FormDataContentType()) + w.WriteHeader(200) + defer writer.Close() + if err := v(writer); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + case MultipleRequestAndResponseTypes200TextResponse: + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + writeRaw(w, ([]byte)(v)) + case MultipleRequestAndResponseTypes400Response: + w.WriteHeader(400) + 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: + 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 TextExample400Response: + w.WriteHeader(400) + 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 UnknownExample400Response: + w.WriteHeader(400) + 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) + } +} + +// 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 UnspecifiedContentType400Response: + w.WriteHeader(400) + case UnspecifiedContentType401Response: + w.WriteHeader(401) + case UnspecifiedContentType403Response: + w.WriteHeader(403) + 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 + + 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 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 URLEncodedExample400Response: + w.WriteHeader(400) + case URLEncodedExampledefaultResponse: + 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) + } +} + +// 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 HeadersExample400Response: + w.WriteHeader(400) + 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) + } +} + +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/+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 +// 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/strict-server/chi/server.go b/internal/test/strict-server/chi/server.go new file mode 100644 index 000000000..876850a97 --- /dev/null +++ b/internal/test/strict-server/chi/server.go @@ -0,0 +1,102 @@ +//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 + +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 MultipleRequestAndResponseTypes400Response{} + } +} + +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}} +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/internal/test/strict-server/chi/types.cfg.yaml b/internal/test/strict-server/chi/types.cfg.yaml new file mode 100644 index 000000000..4ea1d8aa5 --- /dev/null +++ b/internal/test/strict-server/chi/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/chi/types.gen.go b/internal/test/strict-server/chi/types.gen.go new file mode 100644 index 000000000..33827cb7a --- /dev/null +++ b/internal/test/strict-server/chi/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/internal/test/strict-server/client/client.cfg.yaml b/internal/test/strict-server/client/client.cfg.yaml new file mode 100644 index 000000000..637450eaa --- /dev/null +++ b/internal/test/strict-server/client/client.cfg.yaml @@ -0,0 +1,5 @@ +package: api +generate: + models: true + client: true +output: client.gen.go diff --git a/internal/test/strict-server/client/client.gen.go b/internal/test/strict-server/client/client.gen.go new file mode 100644 index 000000000..6c02ddf59 --- /dev/null +++ b/internal/test/strict-server/client/client.gen.go @@ -0,0 +1,1362 @@ +// 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" +) + +// 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 + +// 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) + + // 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) + + 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) + + // 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) + + 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) { + 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) 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 { + 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) 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 { + 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) +} + +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 + 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 +} + +// 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 + 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 +} + +// 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 + 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 +} + +// 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 { + 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) + + // 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) + + 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) + + // 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) + + 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 { + 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 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 +} + +// 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 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 +} + +// 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 +} + +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...) + 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) +} + +// 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...) + 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) +} + +// 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...) + 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) +} + +// 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) + 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 +} + +// 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) + 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 +} + +// 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) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &URLEncodedExampleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + 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/internal/test/strict-server/client/client.go b/internal/test/strict-server/client/client.go new file mode 100644 index 000000000..3b37a171f --- /dev/null +++ b/internal/test/strict-server/client/client.go @@ -0,0 +1,3 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=client.cfg.yaml ../strict-schema.yaml + +package api diff --git a/internal/test/strict-server/echo/server.cfg.yaml b/internal/test/strict-server/echo/server.cfg.yaml new file mode 100644 index 000000000..3c3c5c9c5 --- /dev/null +++ b/internal/test/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/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go new file mode 100644 index 000000000..9ba52dca6 --- /dev/null +++ b/internal/test/strict-server/echo/server.gen.go @@ -0,0 +1,922 @@ +// 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/labstack/echo/v4" +) + +// 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 /reusable-responses) + ReusableResponses(ctx echo.Context) error + + // (POST /text) + TextExample(ctx echo.Context) error + + // (POST /unknown) + UnknownExample(ctx echo.Context) error + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx echo.Context) error + + // (POST /urlencoded) + URLEncodedExample(ctx echo.Context) error + + // (POST /with-headers) + HeadersExample(ctx echo.Context, params HeadersExampleParams) 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 +} + +// 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 + + // 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 +} + +// 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 + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.URLEncodedExample(ctx) + 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 +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+"/reusable-responses", wrapper.ReusableResponses) + 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) + +} + +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 +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400Response = BadrequestResponse + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +type MultipartExample400Response = BadrequestResponse + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +type MultipleRequestAndResponseTypes200TextResponse string + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExample200TextResponse string + +type TextExample400Response = BadrequestResponse + +type TextExampledefaultResponse struct { + StatusCode int +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type UnknownExample400Response = BadrequestResponse + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400Response = BadrequestResponse + +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExample200FormdataResponse Example + +type URLEncodedExample400Response = BadrequestResponse + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400Response = BadrequestResponse + +type HeadersExampledefaultResponse 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 /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) 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{} + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) 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 JSONExample400Response: + return ctx.NoContent(400) + 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 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 + } + case MultipartExample400Response: + return ctx.NoContent(400) + 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 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 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 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 + } + case MultipleRequestAndResponseTypes200TextResponse: + return ctx.Blob(200, "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: + 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 TextExample200TextResponse: + return ctx.Blob(200, "text/plain", []byte(v)) + case TextExample400Response: + return ctx.NoContent(400) + case TextExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + 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 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 UnknownExample400Response: + return ctx.NoContent(400) + case UnknownExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + 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 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: + 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 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 URLEncodedExample400Response: + return ctx.NoContent(400) + case URLEncodedExampledefaultResponse: + return ctx.NoContent(v.StatusCode) + case error: + return v + case nil: + default: + return fmt.Errorf("Unexpected response type: %T", v) + } + 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 HeadersExample400Response: + return ctx.NoContent(400) + 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/+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 +// 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/strict-server/echo/server.go b/internal/test/strict-server/echo/server.go new file mode 100644 index 000000000..876850a97 --- /dev/null +++ b/internal/test/strict-server/echo/server.go @@ -0,0 +1,102 @@ +//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 + +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 MultipleRequestAndResponseTypes400Response{} + } +} + +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}} +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/internal/test/strict-server/echo/types.cfg.yaml b/internal/test/strict-server/echo/types.cfg.yaml new file mode 100644 index 000000000..4ea1d8aa5 --- /dev/null +++ b/internal/test/strict-server/echo/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go 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 000000000..33827cb7a --- /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/internal/test/strict-server/gin/server.cfg.yaml b/internal/test/strict-server/gin/server.cfg.yaml new file mode 100644 index 000000000..66ffd5795 --- /dev/null +++ b/internal/test/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/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go new file mode 100644 index 000000000..56e75529a --- /dev/null +++ b/internal/test/strict-server/gin/server.gen.go @@ -0,0 +1,936 @@ +// 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/gin-gonic/gin" +) + +// 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 /reusable-responses) + ReusableResponses(c *gin.Context) + + // (POST /text) + TextExample(c *gin.Context) + + // (POST /unknown) + UnknownExample(c *gin.Context) + + // (POST /unspecified-content-type) + UnspecifiedContentType(c *gin.Context) + + // (POST /urlencoded) + URLEncodedExample(c *gin.Context) + + // (POST /with-headers) + HeadersExample(c *gin.Context, params HeadersExampleParams) +} + +// 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) +} + +// 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) { + + 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) +} + +// 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) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + 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 + 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+"/reusable-responses", wrapper.ReusableResponses) + + router.POST(options.BaseURL+"/text", wrapper.TextExample) + + 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) + + 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 +} + +type JSONExample200JSONResponse Example + +func (t JSONExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type JSONExample400Response = BadrequestResponse + +type JSONExampledefaultResponse struct { + StatusCode int +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +type MultipartExample400Response = BadrequestResponse + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (t MultipleRequestAndResponseTypes200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal((Example)(t)) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +type MultipleRequestAndResponseTypes200TextResponse string + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +type ReusableResponses400Response = BadrequestResponse + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExample200TextResponse string + +type TextExample400Response = BadrequestResponse + +type TextExampledefaultResponse struct { + StatusCode int +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +type UnknownExample400Response = BadrequestResponse + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +type UnspecifiedContentType400Response = BadrequestResponse + +type UnspecifiedContentType401Response struct { +} + +type UnspecifiedContentType403Response struct { +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExample200FormdataResponse Example + +type URLEncodedExample400Response = BadrequestResponse + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (t HeadersExample200JSONResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) +} + +type HeadersExample400Response = BadrequestResponse + +type HeadersExampledefaultResponse 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 /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) 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{} + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) 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 JSONExample400Response: + ctx.Status(400) + 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 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) + } + case MultipartExample400Response: + ctx.Status(400) + 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 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 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) + } + case MultipleRequestAndResponseTypes200TextResponse: + ctx.Data(200, "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: + 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 TextExample400Response: + ctx.Status(400) + 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 UnknownExample200Videomp4Response: + if closer, ok := v.Body.(io.ReadCloser); ok { + defer closer.Close() + } + ctx.DataFromReader(200, v.ContentLength, "video/mp4", v.Body, nil) + case UnknownExample400Response: + ctx.Status(400) + case UnknownExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// 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 UnspecifiedContentType400Response: + ctx.Status(400) + case UnspecifiedContentType401Response: + ctx.Status(401) + case UnspecifiedContentType403Response: + ctx.Status(403) + 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 + + 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 URLEncodedExample400Response: + ctx.Status(400) + case URLEncodedExampledefaultResponse: + ctx.Status(v.StatusCode) + case error: + ctx.Error(v) + case nil: + default: + ctx.Error(fmt.Errorf("Unexpected response type: %T", v)) + } +} + +// 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 HeadersExample400Response: + ctx.Status(400) + 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/+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 +// 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/strict-server/gin/server.go b/internal/test/strict-server/gin/server.go new file mode 100644 index 000000000..876850a97 --- /dev/null +++ b/internal/test/strict-server/gin/server.go @@ -0,0 +1,102 @@ +//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 + +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 MultipleRequestAndResponseTypes400Response{} + } +} + +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}} +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) interface{} { + return ReusableResponses200JSONResponse{Body: *request.Body} +} diff --git a/internal/test/strict-server/gin/types.cfg.yaml b/internal/test/strict-server/gin/types.cfg.yaml new file mode 100644 index 000000000..4ea1d8aa5 --- /dev/null +++ b/internal/test/strict-server/gin/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go 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 000000000..33827cb7a --- /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/internal/test/strict-server/strict-schema.yaml b/internal/test/strict-server/strict-schema.yaml new file mode 100644 index 000000000..a9a2c4109 --- /dev/null +++ b/internal/test/strict-server/strict-schema.yaml @@ -0,0 +1,253 @@ +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: MultipleRequestAndResponseTypes + 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 + 400: + $ref: "#/components/responses/badrequest" + /with-headers: + post: + operationId: HeadersExample + 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 + /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 + 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" + 401: + $ref: "#/components/responses/badrequest" + 403: + $ref: "#/components/responses/badrequest" + default: + description: Unknown error +components: + responses: + badrequest: + description: BadRequest + reusableresponse: + description: OK + headers: + header1: + schema: + type: string + header2: + schema: + type: integer + content: + application/json: + schema: + $ref: "#/components/schemas/example" + schemas: + example: + type: object + properties: + value: + type: string diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go new file mode 100644 index 000000000..187ca7846 --- /dev/null +++ b/internal/test/strict-server/strict_test.go @@ -0,0 +1,215 @@ +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" + + "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 := api.StrictServer{} + strictHandler := api.NewStrictHandler(server, nil) + r := chi.NewRouter() + handler := api.HandlerFromMux(strictHandler, r) + testImpl(t, handler) +} + +func TestEchoServer(t *testing.T) { + server := api4.StrictServer{} + strictHandler := api4.NewStrictHandler(server, nil) + e := echo.New() + api4.RegisterHandlers(e, strictHandler) + testImpl(t, e) +} + +func TestGinServer(t *testing.T) { + server := api2.StrictServer{} + strictHandler := api2.NewStrictHandler(server, nil) + gin.SetMode(gin.ReleaseMode) + r := gin.New() + 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 := 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 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 := 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 + 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 api3.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 := 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 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 := 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 + 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 api3.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 := 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 api3.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()) + }) + 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 1f827fb9f..e87a2e1cc 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -184,6 +184,23 @@ 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 if opts.Generate.Client { clientOut, err = GenerateClient(t, ops) @@ -271,6 +288,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.Strict { + _, err = w.WriteString(strictServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.Generate.EmbeddedSpec { _, err = w.WriteString(inlinedSpec) if err != nil { diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 10b9e5506..f56acb7ba 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -26,6 +26,7 @@ type GenerateOptions struct { 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 GorillaServer bool `yaml:"gorilla-server,omitempty"` // GorillaServer specifies whether to generate Gorilla 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 9108b6c1a..32c50c0da 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -17,6 +17,8 @@ import ( "bufio" "bytes" "fmt" + "sort" + "strconv" "strings" "text/template" "unicode" @@ -211,6 +213,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} @@ -321,6 +324,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? @@ -339,6 +351,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 @@ -367,6 +382,90 @@ func (r RequestBodyDefinition) Suffix() string { return "With" + r.NameTag + "Body" } +// 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" || r.NameTag == "Formdata" || r.NameTag == "Text" +} + +// Returns true if we support this content type for server. 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 RequestBodyEncoding struct { + ContentType string + Style string + Explode *bool +} + +type ResponseDefinition struct { + StatusCode string + Description string + Contents []ResponseContentDefinition + Headers []ResponseHeaderDefinition + Ref string +} + +func (r ResponseDefinition) HasFixedStatusCode() bool { + _, err := strconv.Atoi(r.StatusCode) + 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 + + // 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.NameTagOrContentType()), + Schema: r.Schema, + } +} + +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, "*") +} + +func (r ResponseContentDefinition) NameTagOrContentType() string { + if r.NameTag != "" { + return r.NameTag + } + return SchemaNameToTypeName(r.ContentType) +} + +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 { @@ -437,6 +536,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"), @@ -449,6 +553,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { Path: requestPath, Spec: op, Bodies: bodyDefinitions, + Responses: responseDefinitions, TypeDefinitions: typeDefinitions, } @@ -514,11 +619,22 @@ 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 strings.HasPrefix(contentType, "multipart/"): + tag = "Multipart" + case contentType == "application/x-www-form-urlencoded": + tag = "Formdata" + case contentType == "text/plain": + tag = "Text" default: + bd := RequestBodyDefinition{ + Required: body.Required, + ContentType: contentType, + } + bodyDefinitions = append(bodyDefinitions, bd) continue } @@ -529,11 +645,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 } @@ -558,11 +674,110 @@ 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 { + return bodyDefinitions[i].ContentType < bodyDefinitions[j].ContentType + }) return bodyDefinitions, typeDefinitions, nil } +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] + if responseOrRef == nil { + continue + } + response := responseOrRef.Value + + var responseContentDefinitions []ResponseContentDefinition + + for _, contentType := range SortedContentKeys(response.Content) { + content := response.Content[contentType] + var tag string + switch { + case contentType == "application/json": + tag = "JSON" + case contentType == "application/x-www-form-urlencoded": + tag = "Formdata" + case strings.HasPrefix(contentType, "multipart/"): + tag = "Multipart" + case contentType == "text/plain": + tag = "Text" + default: + rcd := ResponseContentDefinition{ + ContentType: contentType, + } + responseContentDefinitions = append(responseContentDefinitions, rcd) + 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) + } + + rcd := ResponseContentDefinition{ + ContentType: contentType, + NameTag: tag, + Schema: contentSchema, + } + responseContentDefinitions = append(responseContentDefinitions, rcd) + } + + var responseHeaderDefinitions []ResponseHeaderDefinition + 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) + } + 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 + } + 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) + } + // 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) + } + + return responseDefinitions, nil +} + func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { var typeDefs []TypeDefinition // Start with the params object itself @@ -689,6 +904,24 @@ func GenerateGorillaServer(t *template.Template, operations []OperationDefinitio return GenerateTemplates([]string{"gorilla/gorilla-interface.tmpl", "gorilla/gorilla-middleware.tmpl", "gorilla/gorilla-register.tmpl"}, t, operations) } +func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Configuration) (string, error) { + templates := []string{"strict/strict-interface.tmpl"} + if opts.Generate.ChiServer { + templates = append(templates, "strict/strict-chi.tmpl") + } + if opts.Generate.EchoServer { + templates = append(templates, "strict/strict-echo.tmpl") + } + if opts.Generate.GinServer { + templates = append(templates, "strict/strict-gin.tmpl") + } + 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/schema.go b/pkg/codegen/schema.go index b0f8daff3..f8da94f40 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -529,6 +529,8 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem outSchema.SkipOptionalPointer = true case "uuid": outSchema.GoType = "openapi_types.UUID" + case "binary": + outSchema.GoType = "openapi_types.File" default: // All unrecognized formats are simply a regular string. outSchema.GoType = "string" diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 0d4988766..2b5b7ff1a 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 .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 */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -83,6 +85,7 @@ func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithRespons {{$pathParams := .PathParams -}} {{$bodyRequired := .BodyRequired -}} {{range .Bodies}} +{{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 { @@ -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 b08172881..ffb62bbeb 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 .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 */}} {{end}}{{/* range . $opid := .OperationId */}} } @@ -103,8 +105,9 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge } {{range .Bodies}} +{{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}}{{.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 } @@ -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,16 +129,28 @@ func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathPar {{$opid := .OperationId -}} {{range .Bodies}} +{{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 - 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 -}} {{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 d970ce77a..9661691a1 100644 --- a/pkg/codegen/templates/request-bodies.tmpl +++ b/pkg/codegen/templates/request-bodies.tmpl @@ -1,8 +1,11 @@ {{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 .IsAlias}}={{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 new file mode 100644 index 000000000..ba2da838b --- /dev/null +++ b/pkg/codegen/templates/strict/strict-chi.tmpl @@ -0,0 +1,153 @@ +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 -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = r.Header.Get("Content-Type") + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{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 + } + 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 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 + } + {{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 + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.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}}{{.NameTagOrContentType}}Response: + {{range $headers -}} + w.Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{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)) + } + {{end -}} + w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}v.StatusCode{{end}}) + {{if eq .NameTag "JSON" -}} + 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 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() + } + _, _ = io.Copy(w, v.Body) + {{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 000000000..245f3e59b --- /dev/null +++ b/pkg/codegen/templates/strict/strict-echo.tmpl @@ -0,0 +1,133 @@ +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 -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.Request().Header.Get("Content-Type") + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{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 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 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) + if err != nil { + return err + } + 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 */}} + + 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}}{{.NameTagOrContentType}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "JSON" -}} + 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 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)) + } + 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) -}} + case {{$opid}}{{$statusCode}}Response: + {{range $headers -}} + ctx.Response().Header().Set("{{.Name}}", fmt.Sprint(v.Headers.{{.GoName}})) + {{end -}} + return 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 000000000..f1f7e895c --- /dev/null +++ b/pkg/codegen/templates/strict/strict-gin.tmpl @@ -0,0 +1,133 @@ +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 -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.ContentType() + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{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.Request.ParseForm(); err != nil { + ctx.Error(err) + return + } + 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 reader, err := ctx.Request.MultipartReader(); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader + } else { + ctx.Error(err) + return + } + {{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 + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.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}}{{.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)) + {{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 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() + } + 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) -}} + 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 000000000..91994d547 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -0,0 +1,96 @@ +{{range .}} + {{$opid := .OperationId -}} + type {{$opid | ucFirst}}RequestObject struct { + {{range .PathParams -}} + {{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}} + {{end -}} + {{if .RequiresParamObject -}} + Params {{$opid}}Params + {{end -}} + {{if .HasMaskedRequestContentTypes -}} + ContentType string + {{end -}} + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}} + {{end -}} + } + + {{range .Responses}} + {{$statusCode := .StatusCode -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$isRef := .IsRef -}} + {{$ref := .Ref | ucFirst -}} + + {{if (and $hasHeaders (not $isRef)) -}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers -}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end -}} + } + {{end}} + + {{range .Contents}} + {{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 .Schema.IsRef) (eq .NameTag "JSON")}} + func (t {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(({{.Schema.GoType}})(t)) + } + {{end}} + {{else -}} + 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 {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} + } + {{if eq .NameTag "JSON"}} + func (t {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Body) + } + {{end}} + {{end}} + {{end}} + + {{if eq 0 (len .Contents) -}} + {{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}} + +// 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/strict/strict-responses.tmpl b/pkg/codegen/templates/strict/strict-responses.tmpl new file mode 100644 index 000000000..6034d8fc6 --- /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 diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index c713002b4..557b919f5 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)) diff --git a/pkg/runtime/bindform.go b/pkg/runtime/bindform.go new file mode 100644 index 000000000..b502f55af --- /dev/null +++ b/pkg/runtime/bindform.go @@ -0,0 +1,309 @@ +package runtime + +import ( + "encoding/json" + "errors" + "fmt" + "mime/multipart" + "net/url" + "reflect" + "strconv" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/types" +) + +const tagName = "json" +const jsonContentType = "application/json" + +type RequestBodyEncoding struct { + ContentType string + Style string + 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 { + return errors.New("form data body should be a struct") + } + tValue := ptrVal.Type() + + for i := 0; i < tValue.NumField(); i++ { + 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 + 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, field.Addr().Interface()); err != nil { + return err + } + } + } else { + // regular form data + if _, err := bindFormImpl(field, form, files, tag); err != nil { + return err + } + } + } + + 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 := ptrVal.Field(i) + tag := tValue.Field(i).Tag.Get(tagName) + if !field.CanInterface() || tag == "-" { + continue + } + omitEmpty := strings.HasSuffix(tag, ",omitempty") + 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(field); 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(field, 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() { + 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" && field.Type.Kind() == reflect.Map && tag == "-" { + additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), v, form, files, name) + if err != nil { + return false, err + } + hasData = hasData || additionalPropertiesHasData + } + if !v.Field(i).CanInterface() || 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, 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) { + 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 !v.Field(i).CanInterface() || 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 new file mode 100644 index 000000000..ac47ef2c9 --- /dev/null +++ b/pkg/runtime/bindform_test.go @@ -0,0 +1,196 @@ +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"` + AdditionalProperties map[string]string `json:"-"` + } + 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"}}), + }, + "opt_struct[additional_property]=123": { + OptStruct: &testSubStruct{AdditionalProperties: map[string]string{"additional_property": "123"}}, + }, + } + + 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()) +} + +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 + 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 000000000..f0ad3808c --- /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)) +}