From aac7e584320d983ffbf0ebf0cbdebe80005129c2 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sat, 22 Oct 2022 20:29:53 +0100 Subject: [PATCH 01/42] Revert generated --- internal/test/server/server_moq.gen.go | 104 +++++++++++-------------- 1 file changed, 47 insertions(+), 57 deletions(-) diff --git a/internal/test/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go index 80bc73a9e0..aa22e65ca7 100644 --- a/internal/test/server/server_moq.gen.go +++ b/internal/test/server/server_moq.gen.go @@ -14,46 +14,46 @@ var _ ServerInterface = &ServerInterfaceMock{} // ServerInterfaceMock is a mock implementation of ServerInterface. // -// func TestSomethingThatUsesServerInterface(t *testing.T) { +// func TestSomethingThatUsesServerInterface(t *testing.T) { // -// // make and configure a mocked ServerInterface -// mockedServerInterface := &ServerInterfaceMock{ -// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { -// panic("mock out the CreateResource method") -// }, -// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { -// panic("mock out the CreateResource2 method") -// }, -// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetEveryTypeOptional method") -// }, -// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetReservedKeyword method") -// }, -// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetResponseWithReference method") -// }, -// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetSimple method") -// }, -// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { -// panic("mock out the GetWithArgs method") -// }, -// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { -// panic("mock out the GetWithContentType method") -// }, -// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { -// panic("mock out the GetWithReferences method") -// }, -// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { -// panic("mock out the UpdateResource3 method") -// }, -// } +// // make and configure a mocked ServerInterface +// mockedServerInterface := &ServerInterfaceMock{ +// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { +// panic("mock out the CreateResource method") +// }, +// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { +// panic("mock out the CreateResource2 method") +// }, +// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetEveryTypeOptional method") +// }, +// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetReservedKeyword method") +// }, +// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetResponseWithReference method") +// }, +// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetSimple method") +// }, +// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { +// panic("mock out the GetWithArgs method") +// }, +// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { +// panic("mock out the GetWithContentType method") +// }, +// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { +// panic("mock out the GetWithReferences method") +// }, +// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { +// panic("mock out the UpdateResource3 method") +// }, +// } // -// // use mockedServerInterface in code that requires ServerInterface -// // and then make assertions. +// // use mockedServerInterface in code that requires ServerInterface +// // and then make assertions. // -// } +// } type ServerInterfaceMock struct { // CreateResourceFunc mocks the CreateResource method. CreateResourceFunc func(w http.ResponseWriter, r *http.Request, argument string) @@ -208,8 +208,7 @@ func (mock *ServerInterfaceMock) CreateResource(w http.ResponseWriter, r *http.R // CreateResourceCalls gets all the calls that were made to CreateResource. // Check the length with: -// -// len(mockedServerInterface.CreateResourceCalls()) +// len(mockedServerInterface.CreateResourceCalls()) func (mock *ServerInterfaceMock) CreateResourceCalls() []struct { W http.ResponseWriter R *http.Request @@ -250,8 +249,7 @@ func (mock *ServerInterfaceMock) CreateResource2(w http.ResponseWriter, r *http. // CreateResource2Calls gets all the calls that were made to CreateResource2. // Check the length with: -// -// len(mockedServerInterface.CreateResource2Calls()) +// len(mockedServerInterface.CreateResource2Calls()) func (mock *ServerInterfaceMock) CreateResource2Calls() []struct { W http.ResponseWriter R *http.Request @@ -290,8 +288,7 @@ func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r * // GetEveryTypeOptionalCalls gets all the calls that were made to GetEveryTypeOptional. // Check the length with: -// -// len(mockedServerInterface.GetEveryTypeOptionalCalls()) +// len(mockedServerInterface.GetEveryTypeOptionalCalls()) func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct { W http.ResponseWriter R *http.Request @@ -326,8 +323,7 @@ func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *ht // GetReservedKeywordCalls gets all the calls that were made to GetReservedKeyword. // Check the length with: -// -// len(mockedServerInterface.GetReservedKeywordCalls()) +// len(mockedServerInterface.GetReservedKeywordCalls()) func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct { W http.ResponseWriter R *http.Request @@ -362,8 +358,7 @@ func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter, // GetResponseWithReferenceCalls gets all the calls that were made to GetResponseWithReference. // Check the length with: -// -// len(mockedServerInterface.GetResponseWithReferenceCalls()) +// len(mockedServerInterface.GetResponseWithReferenceCalls()) func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct { W http.ResponseWriter R *http.Request @@ -398,8 +393,7 @@ func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Reques // GetSimpleCalls gets all the calls that were made to GetSimple. // Check the length with: -// -// len(mockedServerInterface.GetSimpleCalls()) +// len(mockedServerInterface.GetSimpleCalls()) func (mock *ServerInterfaceMock) GetSimpleCalls() []struct { W http.ResponseWriter R *http.Request @@ -436,8 +430,7 @@ func (mock *ServerInterfaceMock) GetWithArgs(w http.ResponseWriter, r *http.Requ // GetWithArgsCalls gets all the calls that were made to GetWithArgs. // Check the length with: -// -// len(mockedServerInterface.GetWithArgsCalls()) +// len(mockedServerInterface.GetWithArgsCalls()) func (mock *ServerInterfaceMock) GetWithArgsCalls() []struct { W http.ResponseWriter R *http.Request @@ -476,8 +469,7 @@ func (mock *ServerInterfaceMock) GetWithContentType(w http.ResponseWriter, r *ht // GetWithContentTypeCalls gets all the calls that were made to GetWithContentType. // Check the length with: -// -// len(mockedServerInterface.GetWithContentTypeCalls()) +// len(mockedServerInterface.GetWithContentTypeCalls()) func (mock *ServerInterfaceMock) GetWithContentTypeCalls() []struct { W http.ResponseWriter R *http.Request @@ -518,8 +510,7 @@ func (mock *ServerInterfaceMock) GetWithReferences(w http.ResponseWriter, r *htt // GetWithReferencesCalls gets all the calls that were made to GetWithReferences. // Check the length with: -// -// len(mockedServerInterface.GetWithReferencesCalls()) +// len(mockedServerInterface.GetWithReferencesCalls()) func (mock *ServerInterfaceMock) GetWithReferencesCalls() []struct { W http.ResponseWriter R *http.Request @@ -560,8 +551,7 @@ func (mock *ServerInterfaceMock) UpdateResource3(w http.ResponseWriter, r *http. // UpdateResource3Calls gets all the calls that were made to UpdateResource3. // Check the length with: -// -// len(mockedServerInterface.UpdateResource3Calls()) +// len(mockedServerInterface.UpdateResource3Calls()) func (mock *ServerInterfaceMock) UpdateResource3Calls() []struct { W http.ResponseWriter R *http.Request From a2d3029f2dead8823fb99cdc68609b3801bbd6ca Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sun, 23 Oct 2022 18:18:40 +0100 Subject: [PATCH 02/42] Add to cmd --- cmd/oapi-codegen/oapi-codegen.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index af4361004b..9f4c2f4c10 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -91,7 +91,7 @@ func main() { // All flags below are deprecated, and will be removed in a future release. Please do not // update their behavior. flag.StringVar(&flagGenerate, "generate", "types,client,server,spec", - `Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune"`) + `Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune", "fiber"`) flag.StringVar(&flagIncludeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.") flag.StringVar(&flagExcludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.") flag.StringVar(&flagTemplatesDir, "templates", "", "Path to directory containing user templates") @@ -293,7 +293,7 @@ func loadTemplateOverrides(templatesDir string) (map[string]string, error) { for _, f := range files { // Recursively load subdirectory files, using the path relative to the templates // directory as the key. This allows for overriding the files in the service-specific - // directories (e.g. echo, chi, etc.). + // directories (e.g. echo, chi, fiber, etc.). if f.IsDir() { subFiles, err := loadTemplateOverrides(path.Join(templatesDir, f.Name())) if err != nil { From 6804a3b5ab7a157944789bf6cb44b5821a8c548d Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sun, 23 Oct 2022 18:33:54 +0100 Subject: [PATCH 03/42] Add to codegen --- pkg/codegen/codegen.go | 15 +++++++++++++++ pkg/codegen/configuration.go | 4 ++++ pkg/codegen/operations.go | 9 +++++++++ pkg/codegen/template_helpers.go | 1 + 4 files changed, 29 insertions(+) diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 1cf431ed9c..545170e4eb 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -189,6 +189,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + var fiberServerOut string + if opts.Generate.FiberServer { + fiberServerOut, err = GenerateFiberServer(t, ops) + if err != nil { + return "", fmt.Errorf("error generating Go handlers for Paths: %w", err) + } + } + var ginServerOut string if opts.Generate.GinServer { ginServerOut, err = GenerateGinServer(t, ops) @@ -298,6 +306,13 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { } } + if opts.Generate.FiberServer { + _, err = w.WriteString(fiberServerOut) + if err != nil { + return "", fmt.Errorf("error writing server path handlers: %w", err) + } + } + if opts.Generate.GinServer { _, err = w.WriteString(ginServerOut) if err != nil { diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 8d7f2e6a16..7d5c1af88f 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -23,6 +23,7 @@ type Configuration struct { // GenerateOptions specifies which supported output formats to generate. type GenerateOptions struct { ChiServer bool `yaml:"chi-server,omitempty"` // ChiServer specifies whether to generate chi server boilerplate + FiberServer bool `yaml:"fiber,omitempty"` // FiberServer specifies whether to generate fiber server boilerplate EchoServer bool `yaml:"echo-server,omitempty"` // EchoServer specifies whether to generate echo server boilerplate GinServer bool `yaml:"gin-server,omitempty"` // GinServer specifies whether to generate gin server boilerplate GorillaServer bool `yaml:"gorilla-server,omitempty"` // GorillaServer specifies whether to generate Gorilla server boilerplate @@ -113,6 +114,9 @@ func (o Configuration) Validate() error { if o.Generate.ChiServer { nServers++ } + if o.Generate.FiberServer { + nServers++ + } if o.Generate.EchoServer { nServers++ } diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index ab09547d5a..75568a5dea 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -913,6 +913,12 @@ func GenerateChiServer(t *template.Template, operations []OperationDefinition) ( return GenerateTemplates([]string{"chi/chi-interface.tmpl", "chi/chi-middleware.tmpl", "chi/chi-handler.tmpl"}, t, operations) } +// GenerateFiberServer This function generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateFiberServer(t *template.Template, operations []OperationDefinition) (string, error) { + return GenerateTemplates([]string{"fiber/fiber-interface.tmpl", "fiber/fiber-middleware.tmpl", "fiber/fiber-handler.tmpl"}, t, operations) +} + // GenerateEchoServer This function generates all the go code for the ServerInterface as well as // all the wrapper functions around our handlers. func GenerateEchoServer(t *template.Template, operations []OperationDefinition) (string, error) { @@ -936,6 +942,9 @@ func GenerateStrictServer(t *template.Template, operations []OperationDefinition if opts.Generate.ChiServer || opts.Generate.GorillaServer { templates = append(templates, "strict/strict-http.tmpl") } + if opts.Generate.FiberServer { + templates = append(templates, "strict/strict-fiber.tmpl") + } if opts.Generate.EchoServer { templates = append(templates, "strict/strict-echo.tmpl") } diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index bae1e30ceb..d0c440f286 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -284,6 +284,7 @@ var TemplateFunctions = template.FuncMap{ "genParamNames": genParamNames, "genParamFmtString": ReplacePathParamsWithStr, "swaggerUriToEchoUri": SwaggerUriToEchoUri, + "swaggerUriToFiberUri": SwaggerUriToFiberUri, "swaggerUriToChiUri": SwaggerUriToChiUri, "swaggerUriToGinUri": SwaggerUriToGinUri, "swaggerUriToGorillaUri": SwaggerUriToGorillaUri, From e98165be2aa01c3f955a30ce7127795a90346b73 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sun, 23 Oct 2022 22:06:18 +0100 Subject: [PATCH 04/42] Fiber --- examples/petstore-expanded/fiber/api/cfg.yaml | 6 + .../fiber/api/petstore.gen.go | 423 ++++++++++++++++++ .../petstore-expanded/fiber/api/petstore.go | 131 ++++++ examples/petstore-expanded/fiber/petstore.go | 47 ++ .../petstore-expanded/fiber/petstore_test.go | 168 +++++++ internal/test/server/server_moq.gen.go | 104 +++-- .../templates/fiber/fiber-handler.tmpl | 50 +++ .../templates/fiber/fiber-interface.tmpl | 7 + .../templates/fiber/fiber-middleware.tmpl | 255 +++++++++++ .../templates/strict/strict-fiber.tmpl | 94 ++++ pkg/fiber-middleware/oapi_validate.go | 132 ++++++ pkg/fiber-middleware/oapi_validate_test.go | 411 +++++++++++++++++ pkg/fiber-middleware/test_spec.yaml | 103 +++++ 13 files changed, 1884 insertions(+), 47 deletions(-) create mode 100644 examples/petstore-expanded/fiber/api/cfg.yaml create mode 100644 examples/petstore-expanded/fiber/api/petstore.gen.go create mode 100644 examples/petstore-expanded/fiber/api/petstore.go create mode 100644 examples/petstore-expanded/fiber/petstore.go create mode 100644 examples/petstore-expanded/fiber/petstore_test.go create mode 100644 pkg/codegen/templates/fiber/fiber-handler.tmpl create mode 100644 pkg/codegen/templates/fiber/fiber-interface.tmpl create mode 100644 pkg/codegen/templates/fiber/fiber-middleware.tmpl create mode 100644 pkg/codegen/templates/strict/strict-fiber.tmpl create mode 100644 pkg/fiber-middleware/oapi_validate.go create mode 100644 pkg/fiber-middleware/oapi_validate_test.go create mode 100644 pkg/fiber-middleware/test_spec.yaml diff --git a/examples/petstore-expanded/fiber/api/cfg.yaml b/examples/petstore-expanded/fiber/api/cfg.yaml new file mode 100644 index 0000000000..ea1cd1422d --- /dev/null +++ b/examples/petstore-expanded/fiber/api/cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + fiber: true + models: true + embedded-spec: true +output: petstore.gen.go diff --git a/examples/petstore-expanded/fiber/api/petstore.gen.go b/examples/petstore-expanded/fiber/api/petstore.gen.go new file mode 100644 index 0000000000..f65eef2dab --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore.gen.go @@ -0,0 +1,423 @@ +// 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" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/gofiber/fiber" +) + +// Error defines model for Error. +type Error struct { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit 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 + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(c *fiber.Ctx, params FindPetsParams) + // Creates a new pet + // (POST /pets) + AddPet(c *fiber.Ctx) + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(c *fiber.Ctx, id int64) + // Returns a pet by ID + // (GET /pets/{id}) + FindPetByID(c *fiber.Ctx, id int64) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(c *fiber.Ctx, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// FindPets operation middleware +func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + // ------------- Optional query parameter "tags" ------------- + + 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" ------------- + + 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(c *fiber.Ctx) { + 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(c *fiber.Ctx) { + ctx := r.Context() + + var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { + 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(c *fiber.Ctx) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { + 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(c *fiber.Ctx) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { + 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 UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) 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, FiberServerOptions{}) +} + +type FiberServerOptions struct { + BaseURL string + BaseRouter *fiber.App + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(c *fiber.Ctx, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r fiber.App) http.Handler { + return HandlerWithOptions(si, FiberServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r fiber.App, baseURL string) http.Handler { + return HandlerWithOptions(si, FiberServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options FiberServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = fiber.New() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r fiber.App) { + r.Get(options.BaseURL+"/pets", wrapper.FindPets) + }) + r.Group(func(r fiber.App) { + r.Post(options.BaseURL+"/pets", wrapper.AddPet) + }) + r.Group(func(r fiber.App) { + r.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) + }) + r.Group(func(r fiber.App) { + r.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) + }) + + return r +} + +// 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/EToTWdK0qC1yJjns9lJ0K57kcU7yOhHRzVa1ihQMmVAzSZLTASYAQPQ17ZMIgzk", + "Y8iSUAiWhFISZeBQOfg0UtAnve1vII9keckW61adcWwpZDqaw7wb0a4J3vQ3F5ifnp56rLf7mFazKTbP", + "/rR4/+Hj5w+/e9Pf9GvxrjqGks+flp8pbdjS1cRndc1M5WBxp6zdTXmazmwo5cbK7/ub/kYfHUcKOLKZ", + "m7f1UmdGlHX1xEwZ0h+rZrFzXv9CUlLIgM5VKmGZoq8U5W0W8o1r/V8yJVgry9ZSziDxS/iIHjINYGMY", + "2FOQ4oGy9PAjkqWAGYT8GBNkXLEIZ8g4MoUOAllI6xhsyZDJnyxgAfQkPbyjQBgABVYJNzwgYFkV6gAt", + "MNriuIb28L4kfGApCeLAEVxM5DuIKWAioBUJkKMJXSDbgS0pl6wl4chKyT3cFs7gGaSkkXMHY3EbDph0", + "L0pRk+5AOFgeShDYYOKS4deSJfawCLBGC2sFgTkTjA6FEAa2UrzSsWhFpbngwCNny2EFGESzOebueFUc", + "HjIf15hIEu5J1PXgo6MsTMB+pDSwMvVX3qBvCaHjx4IeBkZlJmGGR81tQ44FQgwgMUlMSgkvKQyH3Xu4", + "S0iZgihMCuyPAEoKCJvoiowosKFAARVwI1c/PJakz1iE45OXlCbWl2jZcT7bpO6gH91RXws5DuhIhR06", + "5dFSQtHE9LuHzyWPFAZWlh2qeYboYurUgZmsqJtrltUqmnUHG1qzLQ5BW1saigfHD5RiDz/G9MBAhbOP", + "w6kMersa26HlwNh/CV/CZxqqEiXDktR8Lj7EVAMoHh2TiqTie9Da8FgfOJHP2XVA5axamuTgivpQ3dnD", + "3RozOdcKY6Q0hVeaq7wksMRi+aE0wnG/j647jd+Qm6TjDaWE3fnWWifAQ3coxMAP6x5+FhjJOQpCWU+O", + "MeZCWkn7IupBqcB9FWjR7bncP2mfVmWyq0AOtgglWJDEWerBtGFB6uGHki0BSe0GQ+FDFWinyJYcJa5w", + "mn/3AV7dUrCaxxafMYDHlaZMblKrhz+XFuqjU92aelSad45QukPzASxWi6StnOzZ0p7MMTWZQzWqWVRg", + "4NAdoUyFGzjzHnBWDJalDKxQc0YosvfZJGTb6Yy0ul8Pd6fCVOYmjGMi4eJPOlczTelO/K2tt/+iZ5wO", + "DfW8Wwxmbn7gMOj5Uo+NpARQynUKOT8sBFfa92HJTijBw9boMGDm5rFQ2h5Pel1numlorHOJkK9n0OUU", + "1S5gSrjV/1m29djT8aQOOOcIPH5lr228+AdKOtEkysVJhZXqWfYNTI49yxmo3xxHd/c6AuVRW0tF/+bm", + "Zj/3UGjz2ji6aXKY/ZoV4vO1tF8b5tok94KI3cUANJLAHkwbj5ZYnPxDeF6D0cb6KxuXQF9Hba3ag9ua", + "zuTiPabtlQFCsY0xXxk13idCqTNboCddux/G6lyjZ3DDrkt0nnMuPtFwYdZ3g3rVtOmUsnwfh+2/jIX9", + "ZH1Jwx2JegyHQb8OsM3plCyp0O6f9MxvWuW/xxoXgtf7dR6dPfOwaxZxJFdewNp1jc0cVq6+tcADapuN", + "zTWLW8hFc7rikdsa3Wzyakdb3GoPGZu2E5apf+gAfWwfPFwo/a1ecv1t6rKXfHeZtQJpKIb/JCFvD2JU", + "FbawuFV4r79QnCt20HFx+63j5/ttvff367Ukset/m1z/s2X8QtGmfl1CabOX6fyteP9S3p+82err6e5+", + "97cAAAD//ykDnxlaEgAA", +} + +// 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/fiber/api/petstore.go b/examples/petstore-expanded/fiber/api/petstore.go new file mode 100644 index 0000000000..bef0f40ba5 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore.go @@ -0,0 +1,131 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml + +package api + +import ( + "fmt" + "net/http" + "sync" + + "github.com/gofiber/fiber/v2" +) + +type PetStore struct { + Pets map[int64]Pet + NextId int64 + Lock sync.Mutex +} + +// Make sure we conform to ServerInterface + +var _ ServerInterface = (*PetStore)(nil) + +func NewPetStore() *PetStore { + + return &PetStore{ + Pets: make(map[int64]Pet), + NextId: 1000, + } +} + +// This function wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(c *fiber.Ctx, code int, message string) error { + + petErr := Error{ + Code: int32(code), + Message: message, + } + + return c.Status(code).JSON(petErr) +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(c *fiber.Ctx, params FindPetsParams) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []Pet + + for _, pet := range p.Pets { + if params.Tags != nil { + // If we have tags, filter pets by tag + for _, t := range *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 params.Limit != nil { + l := int(*params.Limit) + if len(result) >= l { + // We're at the limit + break + } + } + } + + return c.Status(http.StatusOK).JSON(result) +} + +func (p *PetStore) AddPet(c *fiber.Ctx) error { + + // We expect a NewPet object in the request body. + var newPet NewPet + + if err := c.BodyParser(&newPet); err != nil { + return sendPetStoreError(c, http.StatusBadRequest, "Invalid format for NewPet") + } + + // 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 = newPet.Name + pet.Tag = newPet.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 c.Status(http.StatusCreated).JSON(pet) +} + +func (p *PetStore) FindPetByID(c *fiber.Ctx, id int64) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[id] + if !found { + return sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + } + + return c.Status(http.StatusOK).JSON(pet) +} + +func (p *PetStore) DeletePet(c *fiber.Ctx, id int64) error { + + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + return sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + } + delete(p.Pets, id) + + c.Status(http.StatusNoContent) + return nil +} diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go new file mode 100644 index 0000000000..430bddb695 --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore.go @@ -0,0 +1,47 @@ +// 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" + "os" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" + middleware "github.com/deepmap/oapi-codegen/pkg/fiber-middleware" + "github.com/gofiber/fiber/v2" +) + +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() + + // This is how you set up a basic fiber router + app := fiber.New() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + app.Use(middleware.OapiRequestValidator(swagger)) + + // We now register our petStore above as the handler for the interface + api.HandlerFromMux(petStore, app) + + // And we serve HTTP until the world ends. + log.Fatal(app.Listen(fmt.Sprintf("0.0.0.0:%d", *port))) +} diff --git a/examples/petstore-expanded/fiber/petstore_test.go b/examples/petstore-expanded/fiber/petstore_test.go new file mode 100644 index 0000000000..310443bacb --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore_test.go @@ -0,0 +1,168 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" + middleware "github.com/deepmap/oapi-codegen/pkg/fiber-middleware" + "github.com/deepmap/oapi-codegen/pkg/testutil" + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func doGet(t *testing.T, mux *fiber.App, 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 fiber router + r := fiber.New() + + // Use our validation middleware to check all requests against the + // OpenAPI schema. + r.Use(middleware.OapiRequestValidator(swagger)) + + store := api.NewPetStore() + api.HandlerFromMux(store, 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.StatusCreated, rr.Code) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshalling 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: {}, + 2: {}, + } + + // 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: {}, + 2: {}, + } + + // 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: {}, + 2: {}, + } + + // 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 unmarshalling 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/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go index aa22e65ca7..80bc73a9e0 100644 --- a/internal/test/server/server_moq.gen.go +++ b/internal/test/server/server_moq.gen.go @@ -14,46 +14,46 @@ var _ ServerInterface = &ServerInterfaceMock{} // ServerInterfaceMock is a mock implementation of ServerInterface. // -// func TestSomethingThatUsesServerInterface(t *testing.T) { +// func TestSomethingThatUsesServerInterface(t *testing.T) { // -// // make and configure a mocked ServerInterface -// mockedServerInterface := &ServerInterfaceMock{ -// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { -// panic("mock out the CreateResource method") -// }, -// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { -// panic("mock out the CreateResource2 method") -// }, -// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetEveryTypeOptional method") -// }, -// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetReservedKeyword method") -// }, -// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetResponseWithReference method") -// }, -// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetSimple method") -// }, -// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { -// panic("mock out the GetWithArgs method") -// }, -// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { -// panic("mock out the GetWithContentType method") -// }, -// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { -// panic("mock out the GetWithReferences method") -// }, -// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { -// panic("mock out the UpdateResource3 method") -// }, -// } +// // make and configure a mocked ServerInterface +// mockedServerInterface := &ServerInterfaceMock{ +// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { +// panic("mock out the CreateResource method") +// }, +// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { +// panic("mock out the CreateResource2 method") +// }, +// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetEveryTypeOptional method") +// }, +// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetReservedKeyword method") +// }, +// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetResponseWithReference method") +// }, +// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetSimple method") +// }, +// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { +// panic("mock out the GetWithArgs method") +// }, +// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { +// panic("mock out the GetWithContentType method") +// }, +// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { +// panic("mock out the GetWithReferences method") +// }, +// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { +// panic("mock out the UpdateResource3 method") +// }, +// } // -// // use mockedServerInterface in code that requires ServerInterface -// // and then make assertions. +// // use mockedServerInterface in code that requires ServerInterface +// // and then make assertions. // -// } +// } type ServerInterfaceMock struct { // CreateResourceFunc mocks the CreateResource method. CreateResourceFunc func(w http.ResponseWriter, r *http.Request, argument string) @@ -208,7 +208,8 @@ func (mock *ServerInterfaceMock) CreateResource(w http.ResponseWriter, r *http.R // CreateResourceCalls gets all the calls that were made to CreateResource. // Check the length with: -// len(mockedServerInterface.CreateResourceCalls()) +// +// len(mockedServerInterface.CreateResourceCalls()) func (mock *ServerInterfaceMock) CreateResourceCalls() []struct { W http.ResponseWriter R *http.Request @@ -249,7 +250,8 @@ func (mock *ServerInterfaceMock) CreateResource2(w http.ResponseWriter, r *http. // CreateResource2Calls gets all the calls that were made to CreateResource2. // Check the length with: -// len(mockedServerInterface.CreateResource2Calls()) +// +// len(mockedServerInterface.CreateResource2Calls()) func (mock *ServerInterfaceMock) CreateResource2Calls() []struct { W http.ResponseWriter R *http.Request @@ -288,7 +290,8 @@ func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r * // GetEveryTypeOptionalCalls gets all the calls that were made to GetEveryTypeOptional. // Check the length with: -// len(mockedServerInterface.GetEveryTypeOptionalCalls()) +// +// len(mockedServerInterface.GetEveryTypeOptionalCalls()) func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct { W http.ResponseWriter R *http.Request @@ -323,7 +326,8 @@ func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *ht // GetReservedKeywordCalls gets all the calls that were made to GetReservedKeyword. // Check the length with: -// len(mockedServerInterface.GetReservedKeywordCalls()) +// +// len(mockedServerInterface.GetReservedKeywordCalls()) func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct { W http.ResponseWriter R *http.Request @@ -358,7 +362,8 @@ func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter, // GetResponseWithReferenceCalls gets all the calls that were made to GetResponseWithReference. // Check the length with: -// len(mockedServerInterface.GetResponseWithReferenceCalls()) +// +// len(mockedServerInterface.GetResponseWithReferenceCalls()) func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct { W http.ResponseWriter R *http.Request @@ -393,7 +398,8 @@ func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Reques // GetSimpleCalls gets all the calls that were made to GetSimple. // Check the length with: -// len(mockedServerInterface.GetSimpleCalls()) +// +// len(mockedServerInterface.GetSimpleCalls()) func (mock *ServerInterfaceMock) GetSimpleCalls() []struct { W http.ResponseWriter R *http.Request @@ -430,7 +436,8 @@ func (mock *ServerInterfaceMock) GetWithArgs(w http.ResponseWriter, r *http.Requ // GetWithArgsCalls gets all the calls that were made to GetWithArgs. // Check the length with: -// len(mockedServerInterface.GetWithArgsCalls()) +// +// len(mockedServerInterface.GetWithArgsCalls()) func (mock *ServerInterfaceMock) GetWithArgsCalls() []struct { W http.ResponseWriter R *http.Request @@ -469,7 +476,8 @@ func (mock *ServerInterfaceMock) GetWithContentType(w http.ResponseWriter, r *ht // GetWithContentTypeCalls gets all the calls that were made to GetWithContentType. // Check the length with: -// len(mockedServerInterface.GetWithContentTypeCalls()) +// +// len(mockedServerInterface.GetWithContentTypeCalls()) func (mock *ServerInterfaceMock) GetWithContentTypeCalls() []struct { W http.ResponseWriter R *http.Request @@ -510,7 +518,8 @@ func (mock *ServerInterfaceMock) GetWithReferences(w http.ResponseWriter, r *htt // GetWithReferencesCalls gets all the calls that were made to GetWithReferences. // Check the length with: -// len(mockedServerInterface.GetWithReferencesCalls()) +// +// len(mockedServerInterface.GetWithReferencesCalls()) func (mock *ServerInterfaceMock) GetWithReferencesCalls() []struct { W http.ResponseWriter R *http.Request @@ -551,7 +560,8 @@ func (mock *ServerInterfaceMock) UpdateResource3(w http.ResponseWriter, r *http. // UpdateResource3Calls gets all the calls that were made to UpdateResource3. // Check the length with: -// len(mockedServerInterface.UpdateResource3Calls()) +// +// len(mockedServerInterface.UpdateResource3Calls()) func (mock *ServerInterfaceMock) UpdateResource3Calls() []struct { W http.ResponseWriter R *http.Request diff --git a/pkg/codegen/templates/fiber/fiber-handler.tmpl b/pkg/codegen/templates/fiber/fiber-handler.tmpl new file mode 100644 index 0000000000..391fecb3e0 --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-handler.tmpl @@ -0,0 +1,50 @@ +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, FiberServerOptions{}) +} + +type FiberServerOptions struct { + BaseURL string + BaseRouter *fiber.App + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(c *fiber.Ctx, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r fiber.App) http.Handler { + return HandlerWithOptions(si, FiberServerOptions { + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r fiber.App, baseURL string) http.Handler { + return HandlerWithOptions(si, FiberServerOptions { + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options FiberServerOptions) http.Handler { +r := options.BaseRouter + +if r == nil { +r = fiber.New() +} +if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } +} +{{if .}}wrapper := ServerInterfaceWrapper{ +Handler: si, +HandlerMiddlewares: options.Middlewares, +ErrorHandlerFunc: options.ErrorHandlerFunc, +} +{{end}} +{{range .}}r.Group(func(r fiber.App) { +r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) +}) +{{end}} +return r +} diff --git a/pkg/codegen/templates/fiber/fiber-interface.tmpl b/pkg/codegen/templates/fiber/fiber-interface.tmpl new file mode 100644 index 0000000000..ada2614cfb --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(c *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} diff --git a/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/pkg/codegen/templates/fiber/fiber-middleware.tmpl new file mode 100644 index 0000000000..d2a786a023 --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -0,0 +1,255 @@ +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(c *fiber.Ctx, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { + ctx := r.Context() + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = c.Params("{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(c.Params("{{.ParamName}}")), &{{$varName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, c.Params("{{.ParamName}}"), &{{$varName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{end}} + +{{range .SecurityDefinitions}} + ctx = context.WithValue(ctx, {{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{ if (or (or .Required .IsPassThrough) .IsJson) }} + if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + }{{end}} + {{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := r.Header + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) + return + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + {{end}} + + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") + siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) + return + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + var cookie *http.Cookie + + if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'") + siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) + return + } + {{- end}} + {{end}} + {{end}} + + var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { + siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} +{{end}} + +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 UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) 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) +} diff --git a/pkg/codegen/templates/strict/strict-fiber.tmpl b/pkg/codegen/templates/strict/strict-fiber.tmpl new file mode 100644 index 0000000000..c6e93cb8cc --- /dev/null +++ b/pkg/codegen/templates/strict/strict-fiber.tmpl @@ -0,0 +1,94 @@ +type StrictHandlerFunc func(ctx *gin.Context, args interface{}) (interface{}, error) + +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 := io.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{}, error) { + return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { + if err := validResponse.Visit{{$opid}}Response(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + } + } +{{end}} diff --git a/pkg/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go new file mode 100644 index 0000000000..8c37a79e13 --- /dev/null +++ b/pkg/fiber-middleware/oapi_validate.go @@ -0,0 +1,132 @@ +// Package middleware implements middleware function for go-chi or net/http, +// which validates incoming HTTP requests to make sure that they conform to the given OAPI 3.0 specification. +// When OAPI validation failes on the request, we return an HTTP/400. +package middleware + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/gorillamux" +) + +// ErrorHandler is called when there is an error in validation +type ErrorHandler func(w http.ResponseWriter, message string, statusCode int) + +// MultiErrorHandler is called when oapi returns a MultiError type +type MultiErrorHandler func(openapi3.MultiError) (int, error) + +// Options to customize request validation, openapi3filter specified options will be passed through. +type Options struct { + Options openapi3filter.Options + ErrorHandler ErrorHandler + MultiErrorHandler MultiErrorHandler +} + +// OapiRequestValidator Creates middleware to validate request by swagger spec. +// This middleware is good for net/http either since go-chi is 100% compatible with net/http. +func OapiRequestValidator(swagger *openapi3.T) func(next http.Handler) http.Handler { + return OapiRequestValidatorWithOptions(swagger, nil) +} + +// OapiRequestValidatorWithOptions Creates middleware to validate request by swagger spec. +// This middleware is good for net/http either since go-chi is 100% compatible with net/http. +func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) func(next http.Handler) http.Handler { + router, err := gorillamux.NewRouter(swagger) + if err != nil { + panic(err) + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // validate request + if statusCode, err := validateRequest(r, router, options); err != nil { + if options != nil && options.ErrorHandler != nil { + options.ErrorHandler(w, err.Error(), statusCode) + } else { + http.Error(w, err.Error(), statusCode) + } + return + } + + // serve + next.ServeHTTP(w, r) + }) + } + +} + +// This function is called from the middleware above and actually does the work +// of validating a request. +func validateRequest(r *http.Request, router routers.Router, options *Options) (int, error) { + + // Find route + route, pathParams, err := router.FindRoute(r) + if err != nil { + return http.StatusBadRequest, err // We failed to find a matching route for the request. + } + + // Validate request + requestValidationInput := &openapi3filter.RequestValidationInput{ + Request: r, + PathParams: pathParams, + Route: route, + } + + if options != nil { + requestValidationInput.Options = &options.Options + } + + if err := openapi3filter.ValidateRequest(context.Background(), requestValidationInput); err != nil { + me := openapi3.MultiError{} + if errors.As(err, &me) { + errFunc := getMultiErrorHandlerFromOptions(options) + return errFunc(me) + } + + switch e := err.(type) { + case *openapi3filter.RequestError: + // We've got a bad request + // Split up the verbose error by lines and return the first one + // openapi errors seem to be multi-line with a decent message on the first + errorLines := strings.Split(e.Error(), "\n") + return http.StatusBadRequest, fmt.Errorf(errorLines[0]) + case *openapi3filter.SecurityRequirementsError: + return http.StatusUnauthorized, err + default: + // This should never happen today, but if our upstream code changes, + // we don't want to crash the server, so handle the unexpected error. + return http.StatusInternalServerError, fmt.Errorf("error validating route: %s", err.Error()) + } + } + + return http.StatusOK, nil +} + +// attempt to get the MultiErrorHandler from the options. If it is not set, +// return a default handler +func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { + if options == nil { + return defaultMultiErrorHandler + } + + if options.MultiErrorHandler == nil { + return defaultMultiErrorHandler + } + + return options.MultiErrorHandler +} + +// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list +// of all the errors. This method is called if there are no other +// methods defined on the options. +func defaultMultiErrorHandler(me openapi3.MultiError) (int, error) { + return http.StatusBadRequest, me +} diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go new file mode 100644 index 0000000000..7a90226518 --- /dev/null +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -0,0 +1,411 @@ +package middleware + +import ( + "context" + _ "embed" + "errors" + "io" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/deepmap/oapi-codegen/pkg/testutil" + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//go:embed test_spec.yaml +var testSchema []byte + +func doGet(t *testing.T, mux *fiber.App, rawURL string) *httptest.ResponseRecorder { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, mux) + return response.Recorder +} + +func doPost(t *testing.T, mux *fiber.App, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, mux) + return response.Recorder +} + +func TestOapiRequestValidator(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + r := fiber.New() + + // register middleware + r.Use(OapiRequestValidator(swagger)) + + // basic cases + testRequestValidatorBasicFunctions(t, r) +} + +func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + r := fiber.New() + + // Set up an authenticator to check authenticated function. It will allow + // access to "someScope", but disallow others. + options := Options{ + Options: openapi3filter.Options{ + ExcludeRequestBody: false, + ExcludeResponseBody: false, + IncludeResponseStatus: true, + MultiError: true, + }, + } + + // register middleware + r.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + r.Get("/multiparamresource", func(c *fiber.Ctx) error { + called = true + return nil + }) + + // Let's send a good request, it should pass + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") + assert.Equal(t, http.StatusOK, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Let's send a request with a missing parameter, it should return + // a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") + assert.Equal(t, http.StatusBadRequest, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with 2 missing parameters, it should return + // a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource") + assert.Equal(t, http.StatusBadRequest, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "value is required but missing") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with a 1 missing parameter, and another outside + // or the parameters. It should return a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") + assert.Equal(t, http.StatusBadRequest, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "number must be at most 100") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with a parameters that do not meet spec. It should + // return a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") + assert.Equal(t, http.StatusBadRequest, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "number must be at least 10") + } + assert.False(t, called, "Handler should not have been called") + called = false + } +} + +func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + r := fiber.New() + + // Set up an authenticator to check authenticated function. It will allow + // access to "someScope", but disallow others. + options := Options{ + Options: openapi3filter.Options{ + ExcludeRequestBody: false, + ExcludeResponseBody: false, + IncludeResponseStatus: true, + MultiError: true, + }, + MultiErrorHandler: func(me openapi3.MultiError) (int, error) { + return http.StatusTeapot, me + }, + } + + // register middleware + r.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + r.Get("/multiparamresource", func(c *fiber.Ctx) error { + called = true + return nil + }) + + // Let's send a good request, it should pass + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") + assert.Equal(t, http.StatusOK, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Let's send a request with a missing parameter, it should return + // a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") + assert.Equal(t, http.StatusTeapot, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with a 2 missing parameters, it should return + // a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource") + assert.Equal(t, http.StatusTeapot, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "value is required but missing") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with a 1 missing parameter, and another outside + // or the parameters. It should return a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") + assert.Equal(t, http.StatusTeapot, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "number must be at most 100") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Let's send a request with a parameters that do not meet spec. It should + // return a bad status + { + rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") + assert.Equal(t, http.StatusTeapot, rec.Code) + body, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "number must be at least 10") + } + assert.False(t, called, "Handler should not have been called") + called = false + } +} + +func TestOapiRequestValidatorWithOptions(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + r := fiber.New() + + // Set up an authenticator to check authenticated function. It will allow + // access to "someScope", but disallow others. + options := Options{ + ErrorHandler: func(w http.ResponseWriter, message string, statusCode int) { + http.Error(w, "test: "+message, statusCode) + }, + Options: openapi3filter.Options{ + AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { + + for _, s := range input.Scopes { + if s == "someScope" { + return nil + } + } + return errors.New("unauthorized") + }, + }, + } + + // register middleware + r.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + // basic cases + testRequestValidatorBasicFunctions(t, r) + + called := false + + r.Get("/protected_resource", func(c *fiber.Ctx) error { + called = true + c.Status(http.StatusNoContent) + return nil + }) + + // Call a protected function to which we have access + { + rec := doGet(t, r, "http://deepmap.ai/protected_resource") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + r.Get("/protected_resource2", func(c *fiber.Ctx) error { + called = true + c.Status(http.StatusNoContent) + return nil + }) + // Call a protected function to which we dont have access + { + rec := doGet(t, r, "http://deepmap.ai/protected_resource2") + assert.Equal(t, http.StatusUnauthorized, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + + r.Get("/protected_resource_401", func(c *fiber.Ctx) error { + called = true + c.Status(http.StatusNoContent) + return nil + }) + // Call a protected function without credentials + { + rec := doGet(t, r, "http://deepmap.ai/protected_resource_401") + assert.Equal(t, http.StatusUnauthorized, rec.Code) + assert.Equal(t, "test: Security requirements failed\n", rec.Body.String()) + assert.False(t, called, "Handler should not have been called") + called = false + } + +} + +func testRequestValidatorBasicFunctions(t *testing.T, r *fiber.App) { + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + r.Get("/resource", func(*fiber.Ctx) error { + called = true + return nil + }) + + // Let's send the request to the wrong server, this should fail validation + { + rec := doGet(t, r, "http://not.deepmap.ai/resource") + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.False(t, called, "Handler should not have been called") + } + + // Let's send a good request, it should pass + { + rec := doGet(t, r, "http://deepmap.ai/resource") + assert.Equal(t, http.StatusOK, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Send an out-of-spec parameter + { + rec := doGet(t, r, "http://deepmap.ai/resource?id=500") + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Send a bad parameter type + { + rec := doGet(t, r, "http://deepmap.ai/resource?id=foo") + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + + // Add a handler for the POST message + r.Post("/resource", func(c *fiber.Ctx) error { + called = true + c.Status(http.StatusNoContent) + return nil + }) + + called = false + // Send a good request body + { + body := struct { + Name string `json:"name"` + }{ + Name: "Marcin", + } + rec := doPost(t, r, "http://deepmap.ai/resource", body) + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Send a malformed body + { + body := struct { + Name int `json:"name"` + }{ + Name: 7, + } + rec := doPost(t, r, "http://deepmap.ai/resource", body) + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } +} diff --git a/pkg/fiber-middleware/test_spec.yaml b/pkg/fiber-middleware/test_spec.yaml new file mode 100644 index 0000000000..6e0a2415d2 --- /dev/null +++ b/pkg/fiber-middleware/test_spec.yaml @@ -0,0 +1,103 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: TestServer +servers: + - url: http://deepmap.ai/ +paths: + /resource: + get: + operationId: getResource + parameters: + - name: id + in: query + schema: + type: integer + minimum: 10 + maximum: 100 + responses: + '200': + description: success + content: + application/json: + schema: + properties: + name: + type: string + id: + type: integer + post: + operationId: createResource + responses: + '204': + description: No content + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + /protected_resource: + get: + operationId: getProtectedResource + security: + - BearerAuth: + - someScope + responses: + '204': + description: no content + /protected_resource2: + get: + operationId: getProtectedResource + security: + - BearerAuth: + - otherScope + responses: + '204': + description: no content + /protected_resource_401: + get: + operationId: getProtectedResource + security: + - BearerAuth: + - unauthorized + responses: + '401': + description: no content + /multiparamresource: + get: + operationId: getResource + parameters: + - name: id + in: query + required: true + schema: + type: integer + minimum: 10 + maximum: 100 + - name: id2 + required: true + in: query + schema: + type: integer + minimum: 10 + maximum: 100 + responses: + '200': + description: success + content: + application/json: + schema: + properties: + name: + type: string + id: + type: integer +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT From 4b511dff0598cf0b628274adadd0d7eb4923ef88 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Mon, 24 Oct 2022 12:42:38 +0100 Subject: [PATCH 05/42] Fix imports --- pkg/codegen/templates/imports.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl index ef43ef390f..3da81b082b 100644 --- a/pkg/codegen/templates/imports.tmpl +++ b/pkg/codegen/templates/imports.tmpl @@ -28,6 +28,7 @@ import ( "github.com/labstack/echo/v4" "github.com/gin-gonic/gin" "github.com/gorilla/mux" + "github.com/gofiber/fiber/v2" {{- range .ExternalImports}} {{ . }} {{- end}} From 9723041450f40903cd326195b5d1e0e1830302e5 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Mon, 24 Oct 2022 12:43:20 +0100 Subject: [PATCH 06/42] Templates --- .../fiber/api/petstore.gen.go | 109 +++++++++--------- examples/petstore-expanded/fiber/petstore.go | 1 + .../templates/fiber/fiber-handler.tmpl | 33 +++--- .../templates/fiber/fiber-interface.tmpl | 2 +- .../templates/fiber/fiber-middleware.tmpl | 53 +++------ 5 files changed, 94 insertions(+), 104 deletions(-) diff --git a/examples/petstore-expanded/fiber/api/petstore.gen.go b/examples/petstore-expanded/fiber/api/petstore.gen.go index f65eef2dab..82475bd4fa 100644 --- a/examples/petstore-expanded/fiber/api/petstore.gen.go +++ b/examples/petstore-expanded/fiber/api/petstore.gen.go @@ -15,7 +15,7 @@ import ( "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" - "github.com/gofiber/fiber" + "github.com/gofiber/fiber/v2" ) // Error defines model for Error. @@ -64,29 +64,27 @@ type AddPetJSONRequestBody = NewPet type ServerInterface interface { // Returns all pets // (GET /pets) - FindPets(c *fiber.Ctx, params FindPetsParams) + FindPets(c *fiber.Ctx, params FindPetsParams) error // Creates a new pet // (POST /pets) - AddPet(c *fiber.Ctx) + AddPet(c *fiber.Ctx) error // Deletes a pet by ID // (DELETE /pets/{id}) - DeletePet(c *fiber.Ctx, id int64) + DeletePet(c *fiber.Ctx, id int64) error // Returns a pet by ID // (GET /pets/{id}) - FindPetByID(c *fiber.Ctx, id int64) + FindPetByID(c *fiber.Ctx, id int64) error } // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(c *fiber.Ctx, err error) + HandlerMiddlewares []fiber.Handler + ErrorHandlerFunc func(c *fiber.Ctx, err error) error } -type MiddlewareFunc func(http.Handler) http.Handler - // FindPets operation middleware -func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) { +func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) error { ctx := r.Context() var err error @@ -98,46 +96,44 @@ func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) { 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 + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "tags", Err: err}) } // ------------- Optional query parameter "limit" ------------- 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 + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "limit", Err: err}) } - var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { - siw.Handler.FindPets(w, r, params) + var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { + return siw.Handler.FindPets(c, params) }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(c, r.WithContext(ctx)) } // AddPet operation middleware -func (siw *ServerInterfaceWrapper) AddPet(c *fiber.Ctx) { +func (siw *ServerInterfaceWrapper) AddPet(c *fiber.Ctx) error { ctx := r.Context() - var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { - siw.Handler.AddPet(w, r) + var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { + return siw.Handler.AddPet(c) }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(c, r.WithContext(ctx)) } // DeletePet operation middleware -func (siw *ServerInterfaceWrapper) DeletePet(c *fiber.Ctx) { +func (siw *ServerInterfaceWrapper) DeletePet(c *fiber.Ctx) error { ctx := r.Context() var err error @@ -147,23 +143,22 @@ func (siw *ServerInterfaceWrapper) DeletePet(c *fiber.Ctx) { err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "id", Err: err}) } - var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { - siw.Handler.DeletePet(w, r, id) + var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { + return siw.Handler.DeletePet(c, id) }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(c, r.WithContext(ctx)) } // FindPetByID operation middleware -func (siw *ServerInterfaceWrapper) FindPetByID(c *fiber.Ctx) { +func (siw *ServerInterfaceWrapper) FindPetByID(c *fiber.Ctx) error { ctx := r.Context() var err error @@ -173,19 +168,18 @@ func (siw *ServerInterfaceWrapper) FindPetByID(c *fiber.Ctx) { err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "id", Err: err}) } - var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { - siw.Handler.FindPetByID(w, r, id) + var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { + return siw.Handler.FindPetByID(c, id) }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(c, r.WithContext(ctx)) } type UnescapedCookieParamError struct { @@ -257,62 +251,65 @@ 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 { +// Handler creates fiber.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions{}) } type FiberServerOptions struct { BaseURL string BaseRouter *fiber.App - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(c *fiber.Ctx, err error) + Middlewares []fiber.Handler + ErrorHandlerFunc func(c *fiber.Ctx, err error) error } -// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, r fiber.App) http.Handler { +// HandlerFromMux creates fiber.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *fiber.App) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions{ BaseRouter: r, }) } -func HandlerFromMuxWithBaseURL(si ServerInterface, r fiber.App, baseURL string) http.Handler { +func HandlerFromMuxWithBaseURL(si ServerInterface, r *fiber.App, baseURL string) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions{ BaseURL: baseURL, BaseRouter: r, }) } -// HandlerWithOptions creates http.Handler with additional options -func HandlerWithOptions(si ServerInterface, options FiberServerOptions) http.Handler { +// HandlerWithOptions creates fiber.Handler with additional options +func HandlerWithOptions(si ServerInterface, options FiberServerOptions) fiber.Handler { r := options.BaseRouter if r == nil { r = fiber.New() } + if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) { - http.Error(w, err.Error(), http.StatusBadRequest) + options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) error { + + if err != nil { + c.Status(http.StatusBadRequest) + return c.SendString(err.Error()) + } + + return c.SendStatus(http.StatusBadRequest) } } + wrapper := ServerInterfaceWrapper{ Handler: si, HandlerMiddlewares: options.Middlewares, ErrorHandlerFunc: options.ErrorHandlerFunc, } - r.Group(func(r fiber.App) { - r.Get(options.BaseURL+"/pets", wrapper.FindPets) - }) - r.Group(func(r fiber.App) { - r.Post(options.BaseURL+"/pets", wrapper.AddPet) - }) - r.Group(func(r fiber.App) { - r.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) - }) - r.Group(func(r fiber.App) { - r.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) - }) + r.Get(options.BaseURL+"/pets", wrapper.FindPets) + + r.Post(options.BaseURL+"/pets", wrapper.AddPet) + + r.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) + + r.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) return r } diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go index 430bddb695..612faae2a9 100644 --- a/examples/petstore-expanded/fiber/petstore.go +++ b/examples/petstore-expanded/fiber/petstore.go @@ -16,6 +16,7 @@ import ( ) func main() { + var port = flag.Int("port", 8080, "Port for test HTTP server") flag.Parse() diff --git a/pkg/codegen/templates/fiber/fiber-handler.tmpl b/pkg/codegen/templates/fiber/fiber-handler.tmpl index 391fecb3e0..0a2f059146 100644 --- a/pkg/codegen/templates/fiber/fiber-handler.tmpl +++ b/pkg/codegen/templates/fiber/fiber-handler.tmpl @@ -1,50 +1,57 @@ -// Handler creates http.Handler with routing matching OpenAPI spec. -func Handler(si ServerInterface) http.Handler { +// Handler creates fiber.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions{}) } type FiberServerOptions struct { BaseURL string BaseRouter *fiber.App - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(c *fiber.Ctx, err error) + Middlewares []fiber.Handler + ErrorHandlerFunc func(c *fiber.Ctx, err error) error } -// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, r fiber.App) http.Handler { +// HandlerFromMux creates fiber.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *fiber.App) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions { BaseRouter: r, }) } -func HandlerFromMuxWithBaseURL(si ServerInterface, r fiber.App, baseURL string) http.Handler { +func HandlerFromMuxWithBaseURL(si ServerInterface, r *fiber.App, baseURL string) fiber.Handler { return HandlerWithOptions(si, FiberServerOptions { BaseURL: baseURL, BaseRouter: r, }) } -// HandlerWithOptions creates http.Handler with additional options -func HandlerWithOptions(si ServerInterface, options FiberServerOptions) http.Handler { +// HandlerWithOptions creates fiber.Handler with additional options +func HandlerWithOptions(si ServerInterface, options FiberServerOptions) fiber.Handler { r := options.BaseRouter if r == nil { r = fiber.New() } + if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) { - http.Error(w, err.Error(), http.StatusBadRequest) + options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) error { + + if err != nil { + c.Status(http.StatusBadRequest) + return c.SendString(err.Error()) + } + + return c.SendStatus(http.StatusBadRequest) } } + {{if .}}wrapper := ServerInterfaceWrapper{ Handler: si, HandlerMiddlewares: options.Middlewares, ErrorHandlerFunc: options.ErrorHandlerFunc, } {{end}} -{{range .}}r.Group(func(r fiber.App) { +{{range .}} r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) -}) {{end}} return r } diff --git a/pkg/codegen/templates/fiber/fiber-interface.tmpl b/pkg/codegen/templates/fiber/fiber-interface.tmpl index ada2614cfb..8ef90a851a 100644 --- a/pkg/codegen/templates/fiber/fiber-interface.tmpl +++ b/pkg/codegen/templates/fiber/fiber-interface.tmpl @@ -2,6 +2,6 @@ type ServerInterface interface { {{range .}}{{.SummaryAsComment }} // ({{.Method}} {{.Path}}) -{{.OperationId}}(c *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{.OperationId}}(c *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error {{end}} } diff --git a/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/pkg/codegen/templates/fiber/fiber-middleware.tmpl index d2a786a023..d28e2a6c13 100644 --- a/pkg/codegen/templates/fiber/fiber-middleware.tmpl +++ b/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -1,16 +1,14 @@ // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(c *fiber.Ctx, err error) + HandlerMiddlewares []fiber.Handler + ErrorHandlerFunc func(c *fiber.Ctx, err error) error } -type MiddlewareFunc func(http.Handler) http.Handler - {{range .}}{{$opid := .OperationId}} // {{$opid}} operation middleware -func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { +func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { ctx := r.Context() {{if or .RequiresParamObject (gt (len .PathParams) 0) }} var err error @@ -25,15 +23,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { {{if .IsJson}} err = json.Unmarshal([]byte(c.Params("{{.ParamName}}")), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) } {{end}} {{if .IsStyled}} err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, c.Params("{{.ParamName}}"), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) } {{end}} @@ -62,22 +58,19 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} }{{if .Required}} else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) - return + return siw.ErrorHandlerFunc(c, &RequiredParamError{ParamName: "{{.ParamName}}"}) }{{end}} {{end}} {{if .IsStyled}} err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) } {{end}} {{end}} @@ -90,8 +83,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { var {{.GoName}} {{.TypeDef}} n := len(valueList) if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) - return + return siw.ErrorHandlerFunc(c, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) } {{if .IsPassThrough}} @@ -101,16 +93,14 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) } {{end}} {{if .IsStyled}} err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) } {{end}} @@ -118,8 +108,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { } {{if .Required}}else { err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") - siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) }{{end}} {{end}} @@ -140,14 +129,12 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { decoded, err := url.QueryUnescape(cookie.Value) if err != nil { err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'") - siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) } params.{{.GoName}} = {{if not .Required}}&{{end}}value @@ -157,8 +144,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { var value {{.TypeDef}} err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) - return + return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} @@ -166,22 +152,21 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) { } {{- if .Required}} else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) - return + return siw.ErrorHandlerFunc(c, &RequiredParamError{ParamName: "{{.ParamName}}"}) } {{- end}} {{end}} {{end}} - var handler http.Handler = http.HandlerFunc(func(c *fiber.Ctx) { - siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error{ + return siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) }) for _, middleware := range siw.HandlerMiddlewares { handler = middleware(handler) } - handler.ServeHTTP(w, r.WithContext(ctx)) + handler.ServeHTTP(c, r.WithContext(ctx)) } {{end}} From 95bac95eff8809980b10bb3ab1b56f5ee12236e8 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Mon, 24 Oct 2022 22:01:43 +0100 Subject: [PATCH 07/42] Split types and code --- examples/petstore-expanded/fiber/api/petstore.go | 3 ++- .../petstore-expanded/fiber/api/{cfg.yaml => server.cfg.yaml} | 3 +-- examples/petstore-expanded/fiber/api/types.cfg.yaml | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) rename examples/petstore-expanded/fiber/api/{cfg.yaml => server.cfg.yaml} (60%) create mode 100644 examples/petstore-expanded/fiber/api/types.cfg.yaml diff --git a/examples/petstore-expanded/fiber/api/petstore.go b/examples/petstore-expanded/fiber/api/petstore.go index bef0f40ba5..60ecd58ff0 100644 --- a/examples/petstore-expanded/fiber/api/petstore.go +++ b/examples/petstore-expanded/fiber/api/petstore.go @@ -1,4 +1,5 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=types.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml package api diff --git a/examples/petstore-expanded/fiber/api/cfg.yaml b/examples/petstore-expanded/fiber/api/server.cfg.yaml similarity index 60% rename from examples/petstore-expanded/fiber/api/cfg.yaml rename to examples/petstore-expanded/fiber/api/server.cfg.yaml index ea1cd1422d..dca16f7923 100644 --- a/examples/petstore-expanded/fiber/api/cfg.yaml +++ b/examples/petstore-expanded/fiber/api/server.cfg.yaml @@ -1,6 +1,5 @@ package: api generate: fiber: true - models: true embedded-spec: true -output: petstore.gen.go +output: petstore-server.gen.go diff --git a/examples/petstore-expanded/fiber/api/types.cfg.yaml b/examples/petstore-expanded/fiber/api/types.cfg.yaml new file mode 100644 index 0000000000..9ac30e11e9 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: petstore-types.gen.go From cd77c32ee1f82d1414320059e7a6aac3694926c6 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Tue, 25 Oct 2022 08:34:28 +0100 Subject: [PATCH 08/42] Rename aliases --- internal/test/strict-server/strict_test.go | 43 +++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index 860cc4cb8d..b709ff2bec 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -17,9 +17,10 @@ import ( "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" + clientAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" + echoAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" + fiberAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/fiber" + ginAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/gin" "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/deepmap/oapi-codegen/pkg/testutil" ) @@ -33,37 +34,37 @@ func TestChiServer(t *testing.T) { } func TestEchoServer(t *testing.T) { - server := api4.StrictServer{} - strictHandler := api4.NewStrictHandler(server, nil) + server := echoAPI.StrictServer{} + strictHandler := echoAPI.NewStrictHandler(server, nil) e := echo.New() - api4.RegisterHandlers(e, strictHandler) + echoAPI.RegisterHandlers(e, strictHandler) testImpl(t, e) } func TestGinServer(t *testing.T) { - server := api2.StrictServer{} - strictHandler := api2.NewStrictHandler(server, nil) + server := ginAPI.StrictServer{} + strictHandler := ginAPI.NewStrictHandler(server, nil) gin.SetMode(gin.ReleaseMode) r := gin.New() - api2.RegisterHandlers(r, strictHandler) + ginAPI.RegisterHandlers(r, strictHandler) testImpl(t, r) } func testImpl(t *testing.T, handler http.Handler) { t.Run("JSONExample", func(t *testing.T) { value := "123" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.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 + var responseBody clientAPI.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} + requestBody := clientAPI.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/urlencoded").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -71,7 +72,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody api3.Example + var responseBody clientAPI.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -116,18 +117,18 @@ func testImpl(t *testing.T, handler http.Handler) { }) t.Run("MultipleRequestAndResponseTypesJSON", func(t *testing.T) { value := "123" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.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 + var responseBody clientAPI.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} + requestBody := clientAPI.Example{Value: &value} requestBodyEncoded, err := runtime.MarshalForm(&requestBody, nil) assert.NoError(t, err) rr := testutil.NewRequest().Post("/multiple").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(requestBodyEncoded.Encode())).GoWithHTTPHandler(t, handler).Recorder @@ -135,7 +136,7 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, "application/x-www-form-urlencoded", rr.Header().Get("Content-Type")) values, err := url.ParseQuery(rr.Body.String()) assert.NoError(t, err) - var responseBody api3.Example + var responseBody clientAPI.Example err = runtime.BindForm(&responseBody, values, nil, nil) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -182,11 +183,11 @@ func testImpl(t *testing.T, handler http.Handler) { header1 := "value1" header2 := "890" value := "asdf" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.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 + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) @@ -203,11 +204,11 @@ func testImpl(t *testing.T, handler http.Handler) { }) t.Run("ReusableResponses", func(t *testing.T) { value := "jkl;" - requestBody := api3.Example{Value: &value} + requestBody := clientAPI.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 + var responseBody clientAPI.Example err := json.NewDecoder(rr.Body).Decode(&responseBody) assert.NoError(t, err) assert.Equal(t, requestBody, responseBody) From d973a93786a6bc7d9cddb6747da5b2b0cfcbc418 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Tue, 25 Oct 2022 09:16:40 +0100 Subject: [PATCH 09/42] Strict --- .../test/strict-server/fiber/server.cfg.yaml | 6 + .../test/strict-server/fiber/server.gen.go | 1034 +++++++++++++++++ internal/test/strict-server/fiber/server.go | 129 ++ .../test/strict-server/fiber/types.cfg.yaml | 4 + .../test/strict-server/fiber/types.gen.go | 54 + internal/test/strict-server/strict_test.go | 12 +- pkg/codegen/operations.go | 17 +- .../strict/strict-fiber-interface.tmpl | 136 +++ .../templates/strict/strict-fiber.tmpl | 50 +- 9 files changed, 1402 insertions(+), 40 deletions(-) create mode 100644 internal/test/strict-server/fiber/server.cfg.yaml create mode 100644 internal/test/strict-server/fiber/server.gen.go create mode 100644 internal/test/strict-server/fiber/server.go create mode 100644 internal/test/strict-server/fiber/types.cfg.yaml create mode 100644 internal/test/strict-server/fiber/types.gen.go create mode 100644 pkg/codegen/templates/strict/strict-fiber-interface.tmpl diff --git a/internal/test/strict-server/fiber/server.cfg.yaml b/internal/test/strict-server/fiber/server.cfg.yaml new file mode 100644 index 0000000000..dd84594d48 --- /dev/null +++ b/internal/test/strict-server/fiber/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + fiber: true + strict-server: true + embedded-spec: true +output: server.gen.go diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go new file mode 100644 index 0000000000..97ad0c0ab9 --- /dev/null +++ b/internal/test/strict-server/fiber/server.gen.go @@ -0,0 +1,1034 @@ +// 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" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/gofiber/fiber/v2" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /json) + JSONExample(c *fiber.Ctx) error + + // (POST /multipart) + MultipartExample(c *fiber.Ctx) error + + // (POST /multiple) + MultipleRequestAndResponseTypes(c *fiber.Ctx) error + + // (POST /reusable-responses) + ReusableResponses(c *fiber.Ctx) error + + // (POST /text) + TextExample(c *fiber.Ctx) error + + // (POST /unknown) + UnknownExample(c *fiber.Ctx) error + + // (POST /unspecified-content-type) + UnspecifiedContentType(c *fiber.Ctx) error + + // (POST /urlencoded) + URLEncodedExample(c *fiber.Ctx) error + + // (POST /with-headers) + HeadersExample(c *fiber.Ctx, params HeadersExampleParams) error +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +type MiddlewareFunc fiber.Handler + +// JSONExample operation middleware +func (siw *ServerInterfaceWrapper) JSONExample(c *fiber.Ctx) error { + + return siw.Handler.JSONExample(c) +} + +// MultipartExample operation middleware +func (siw *ServerInterfaceWrapper) MultipartExample(c *fiber.Ctx) error { + + return siw.Handler.MultipartExample(c) +} + +// MultipleRequestAndResponseTypes operation middleware +func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *fiber.Ctx) error { + + return siw.Handler.MultipleRequestAndResponseTypes(c) +} + +// ReusableResponses operation middleware +func (siw *ServerInterfaceWrapper) ReusableResponses(c *fiber.Ctx) error { + + return siw.Handler.ReusableResponses(c) +} + +// TextExample operation middleware +func (siw *ServerInterfaceWrapper) TextExample(c *fiber.Ctx) error { + + return siw.Handler.TextExample(c) +} + +// UnknownExample operation middleware +func (siw *ServerInterfaceWrapper) UnknownExample(c *fiber.Ctx) error { + + return siw.Handler.UnknownExample(c) +} + +// UnspecifiedContentType operation middleware +func (siw *ServerInterfaceWrapper) UnspecifiedContentType(c *fiber.Ctx) error { + + return siw.Handler.UnspecifiedContentType(c) +} + +// URLEncodedExample operation middleware +func (siw *ServerInterfaceWrapper) URLEncodedExample(c *fiber.Ctx) error { + + return siw.Handler.URLEncodedExample(c) +} + +// HeadersExample operation middleware +func (siw *ServerInterfaceWrapper) HeadersExample(c *fiber.Ctx) error { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := c.GetReqHeaders() + + // ------------- Required header parameter "header1" ------------- + if value, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + + err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, value, &Header1) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter header1: %w", err).Error()) + } + + params.Header1 = Header1 + + } else { + err = fmt.Errorf("Header parameter header1 is required, but not found: %w", err) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + // ------------- Optional header parameter "header2" ------------- + if value, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + + err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, value, &Header2) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter header2: %w", err).Error()) + } + + params.Header2 = &Header2 + + } + + return siw.Handler.HeadersExample(c, params) +} + +// FiberServerOptions provides options for the Fiber server. +type FiberServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + for _, m := range options.Middlewares { + router.Use(m) + } + + 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) + +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +type JSONExampleRequestObject struct { + Body *JSONExampleJSONRequestBody +} + +type JSONExampleResponseObject interface { + VisitJSONExampleResponse(ctx *fiber.Ctx) error +} + +type JSONExample200JSONResponse Example + +func (response JSONExample200JSONResponse) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type JSONExample400Response = BadrequestResponse + +func (response JSONExample400Response) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type JSONExampledefaultResponse struct { + StatusCode int +} + +func (response JSONExampledefaultResponse) VisitJSONExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type MultipartExampleRequestObject struct { + Body *multipart.Reader +} + +type MultipartExampleResponseObject interface { + VisitMultipartExampleResponse(ctx *fiber.Ctx) error +} + +type MultipartExample200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipartExample200MultipartResponse) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + ctx.Response().Header.Set("Content-Type", writer.FormDataContentType()) + ctx.Status(200) + + defer writer.Close() + return response(writer) +} + +type MultipartExample400Response = BadrequestResponse + +func (response MultipartExample400Response) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type MultipartExampledefaultResponse struct { + StatusCode int +} + +func (response MultipartExampledefaultResponse) VisitMultipartExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type MultipleRequestAndResponseTypesRequestObject struct { + JSONBody *MultipleRequestAndResponseTypesJSONRequestBody + FormdataBody *MultipleRequestAndResponseTypesFormdataRequestBody + Body io.Reader + MultipartBody *multipart.Reader + TextBody *MultipleRequestAndResponseTypesTextRequestBody +} + +type MultipleRequestAndResponseTypesResponseObject interface { + VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error +} + +type MultipleRequestAndResponseTypes200JSONResponse Example + +func (response MultipleRequestAndResponseTypes200JSONResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type MultipleRequestAndResponseTypes200FormdataResponse Example + +func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/x-www-form-urlencoded") + ctx.Status(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.Write([]byte(form.Encode())) + return err + } +} + +type MultipleRequestAndResponseTypes200ImagepngResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "image/png") + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type MultipleRequestAndResponseTypes200MultipartResponse func(writer *multipart.Writer) error + +func (response MultipleRequestAndResponseTypes200MultipartResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + ctx.Response().Header.Set("Content-Type", writer.FormDataContentType()) + ctx.Status(200) + + defer writer.Close() + return response(writer) +} + +type MultipleRequestAndResponseTypes200TextResponse string + +func (response MultipleRequestAndResponseTypes200TextResponse) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(ctx *fiber.Ctx) error +} + +type ReusableResponses200JSONResponse = ReusableresponseJSONResponse + +func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response.Body) +} + +type ReusableResponses400Response = BadrequestResponse + +func (response ReusableResponses400Response) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type ReusableResponsesdefaultResponse struct { + StatusCode int +} + +func (response ReusableResponsesdefaultResponse) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type TextExampleRequestObject struct { + Body *TextExampleTextRequestBody +} + +type TextExampleResponseObject interface { + VisitTextExampleResponse(ctx *fiber.Ctx) error +} + +type TextExample200TextResponse string + +func (response TextExample200TextResponse) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + +type TextExample400Response = BadrequestResponse + +func (response TextExample400Response) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type TextExampledefaultResponse struct { + StatusCode int +} + +func (response TextExampledefaultResponse) VisitTextExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type UnknownExampleRequestObject struct { + Body io.Reader +} + +type UnknownExampleResponseObject interface { + VisitUnknownExampleResponse(ctx *fiber.Ctx) error +} + +type UnknownExample200Videomp4Response struct { + Body io.Reader + ContentLength int64 +} + +func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "video/mp4") + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type UnknownExample400Response = BadrequestResponse + +func (response UnknownExample400Response) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type UnknownExampledefaultResponse struct { + StatusCode int +} + +func (response UnknownExampledefaultResponse) VisitUnknownExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type UnspecifiedContentTypeRequestObject struct { + ContentType string + Body io.Reader +} + +type UnspecifiedContentTypeResponseObject interface { + VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error +} + +type UnspecifiedContentType200VideoResponse struct { + Body io.Reader + ContentType string + ContentLength int64 +} + +func (response UnspecifiedContentType200VideoResponse) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", response.ContentType) + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + ctx.Status(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err +} + +type UnspecifiedContentType400Response = BadrequestResponse + +func (response UnspecifiedContentType400Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type UnspecifiedContentType401Response struct { +} + +func (response UnspecifiedContentType401Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(401) + return nil +} + +type UnspecifiedContentType403Response struct { +} + +func (response UnspecifiedContentType403Response) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(403) + return nil +} + +type UnspecifiedContentTypedefaultResponse struct { + StatusCode int +} + +func (response UnspecifiedContentTypedefaultResponse) VisitUnspecifiedContentTypeResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type URLEncodedExampleRequestObject struct { + Body *URLEncodedExampleFormdataRequestBody +} + +type URLEncodedExampleResponseObject interface { + VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error +} + +type URLEncodedExample200FormdataResponse Example + +func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/x-www-form-urlencoded") + ctx.Status(200) + + if form, err := runtime.MarshalForm(response, nil); err != nil { + return err + } else { + _, err := ctx.Write([]byte(form.Encode())) + return err + } +} + +type URLEncodedExample400Response = BadrequestResponse + +func (response URLEncodedExample400Response) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type URLEncodedExampledefaultResponse struct { + StatusCode int +} + +func (response URLEncodedExampledefaultResponse) VisitURLEncodedExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +type HeadersExampleRequestObject struct { + Params HeadersExampleParams + Body *HeadersExampleJSONRequestBody +} + +type HeadersExampleResponseObject interface { + VisitHeadersExampleResponse(ctx *fiber.Ctx) error +} + +type HeadersExample200ResponseHeaders struct { + Header1 string + Header2 int +} + +type HeadersExample200JSONResponse struct { + Body Example + Headers HeadersExample200ResponseHeaders +} + +func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) + ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response.Body) +} + +type HeadersExample400Response = BadrequestResponse + +func (response HeadersExample400Response) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + return nil +} + +type HeadersExampledefaultResponse struct { + StatusCode int +} + +func (response HeadersExampledefaultResponse) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { + ctx.Status(response.StatusCode) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /json) + JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) + + // (POST /multipart) + MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) + + // (POST /multiple) + MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + + // (POST /reusable-responses) + ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) + + // (POST /text) + TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) + + // (POST /unknown) + UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) + + // (POST /unspecified-content-type) + UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) + + // (POST /urlencoded) + URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) + + // (POST /with-headers) + HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) +} + +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) + +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 *fiber.Ctx) error { + var request JSONExampleRequestObject + + var body JSONExampleJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx.UserContext(), request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + if err := validResponse.VisitJSONExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// MultipartExample operation middleware +func (sh *strictHandler) MultipartExample(ctx *fiber.Ctx) error { + var request MultipartExampleRequestObject + + request.Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx.UserContext(), request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + if err := validResponse.VisitMultipartExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// MultipleRequestAndResponseTypes operation middleware +func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error { + var request MultipleRequestAndResponseTypesRequestObject + + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.JSONBody = &body + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "application/x-www-form-urlencoded") { + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.FormdataBody = &body + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "image/png") { + request.Body = bytes.NewReader(ctx.Request().Body()) + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "multipart/form-data") { + request.MultipartBody = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) + } + if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "text/plain") { + data := ctx.Request().Body() + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx.UserContext(), request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + if err := validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// ReusableResponses operation middleware +func (sh *strictHandler) ReusableResponses(ctx *fiber.Ctx) error { + var request ReusableResponsesRequestObject + + var body ReusableResponsesJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx.UserContext(), request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + if err := validResponse.VisitReusableResponsesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// TextExample operation middleware +func (sh *strictHandler) TextExample(ctx *fiber.Ctx) error { + var request TextExampleRequestObject + + data := ctx.Request().Body() + body := TextExampleTextRequestBody(data) + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx.UserContext(), request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + if err := validResponse.VisitTextExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// UnknownExample operation middleware +func (sh *strictHandler) UnknownExample(ctx *fiber.Ctx) error { + var request UnknownExampleRequestObject + + request.Body = bytes.NewReader(ctx.Request().Body()) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnknownExample(ctx.UserContext(), request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + if err := validResponse.VisitUnknownExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// UnspecifiedContentType operation middleware +func (sh *strictHandler) UnspecifiedContentType(ctx *fiber.Ctx) error { + var request UnspecifiedContentTypeRequestObject + + request.ContentType = string(ctx.Request().Header.ContentType()) + + request.Body = bytes.NewReader(ctx.Request().Body()) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx.UserContext(), request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + if err := validResponse.VisitUnspecifiedContentTypeResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// URLEncodedExample operation middleware +func (sh *strictHandler) URLEncodedExample(ctx *fiber.Ctx) error { + var request URLEncodedExampleRequestObject + + var body URLEncodedExampleFormdataRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx.UserContext(), request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + if err := validResponse.VisitURLEncodedExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// HeadersExample operation middleware +func (sh *strictHandler) HeadersExample(ctx *fiber.Ctx, params HeadersExampleParams) error { + var request HeadersExampleRequestObject + + request.Params = params + + var body HeadersExampleJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx.UserContext(), request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + if err := validResponse.VisitHeadersExampleResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + 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/fiber/server.go b/internal/test/strict-server/fiber/server.go new file mode 100644 index 0000000000..fab221b40d --- /dev/null +++ b/internal/test/strict-server/fiber/server.go @@ -0,0 +1,129 @@ +//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) (JSONExampleResponseObject, error) { + return JSONExample200JSONResponse(*request.Body), nil +} + +func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExampleRequestObject) (MultipartExampleResponseObject, error) { + return MultipartExample200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.Body.NextPart() + if err == io.EOF { + return nil + } + 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 + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{ + Body: request.Body, + ContentLength: 0, + }, nil + case request.JSONBody != nil: + return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil + case request.FormdataBody != nil: + return MultipleRequestAndResponseTypes200FormdataResponse(*request.FormdataBody), nil + case request.TextBody != nil: + return MultipleRequestAndResponseTypes200TextResponse(*request.TextBody), nil + case request.MultipartBody != nil: + return MultipleRequestAndResponseTypes200MultipartResponse(func(writer *multipart.Writer) error { + for { + part, err := request.MultipartBody.NextPart() + if err == io.EOF { + return nil + } + 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 + } + } + }), nil + default: + return MultipleRequestAndResponseTypes400Response{}, nil + } +} + +func (s StrictServer) TextExample(ctx context.Context, request TextExampleRequestObject) (TextExampleResponseObject, error) { + return TextExample200TextResponse(*request.Body), nil +} + +func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { + return UnknownExample200Videomp4Response{ + Body: request.Body, + ContentLength: 0, + }, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{ + Body: request.Body, + ContentType: request.ContentType, + ContentLength: 0, + }, nil +} + +func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { + return URLEncodedExample200FormdataResponse(*request.Body), nil +} + +func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { + return HeadersExample200JSONResponse{ + Body: *request.Body, + Headers: HeadersExample200ResponseHeaders{ + Header1: request.Params.Header1, + Header2: *request.Params.Header2, + }, + }, nil +} + +func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { + return ReusableResponses200JSONResponse{ + Body: *request.Body, + Headers: ReusableresponseResponseHeaders{}, + }, nil +} diff --git a/internal/test/strict-server/fiber/types.cfg.yaml b/internal/test/strict-server/fiber/types.cfg.yaml new file mode 100644 index 0000000000..4ea1d8aa5b --- /dev/null +++ b/internal/test/strict-server/fiber/types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: types.gen.go diff --git a/internal/test/strict-server/fiber/types.gen.go b/internal/test/strict-server/fiber/types.gen.go new file mode 100644 index 0000000000..33827cb7a4 --- /dev/null +++ b/internal/test/strict-server/fiber/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_test.go b/internal/test/strict-server/strict_test.go index b709ff2bec..40da18ef4e 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -13,9 +13,11 @@ import ( "github.com/gin-gonic/gin" "github.com/go-chi/chi/v5" + "github.com/gofiber/adaptor/v2" + "github.com/gofiber/fiber/v2" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - + "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" clientAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" echoAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" @@ -50,6 +52,14 @@ func TestGinServer(t *testing.T) { testImpl(t, r) } +func TestFiberServer(t *testing.T) { + server := fiberAPI.StrictServer{} + strictHandler := fiberAPI.NewStrictHandler(server, nil) + r := fiber.New() + fiberAPI.RegisterHandlers(r, strictHandler) + testImpl(t, adaptor.FiberApp(r)) +} + func testImpl(t *testing.T, handler http.Handler) { t.Run("JSONExample", func(t *testing.T) { value := "123" diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 75568a5dea..c5601293ed 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -938,19 +938,22 @@ func GenerateGorillaServer(t *template.Template, operations []OperationDefinitio } func GenerateStrictServer(t *template.Template, operations []OperationDefinition, opts Configuration) (string, error) { - templates := []string{"strict/strict-interface.tmpl"} + + var templates []string + if opts.Generate.ChiServer || opts.Generate.GorillaServer { - templates = append(templates, "strict/strict-http.tmpl") - } - if opts.Generate.FiberServer { - templates = append(templates, "strict/strict-fiber.tmpl") + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-http.tmpl") } if opts.Generate.EchoServer { - templates = append(templates, "strict/strict-echo.tmpl") + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-echo.tmpl") } if opts.Generate.GinServer { - templates = append(templates, "strict/strict-gin.tmpl") + templates = append(templates, "strict/strict-interface.tmpl", "strict/strict-gin.tmpl") } + if opts.Generate.FiberServer { + templates = append(templates, "strict/strict-fiber-interface.tmpl", "strict/strict-fiber.tmpl") + } + return GenerateTemplates(templates, t, operations) } diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl new file mode 100644 index 0000000000..683f842ea2 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -0,0 +1,136 @@ +{{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 -}} + } + + type {{$opid | ucFirst}}ResponseObject interface { + Visit{{$opid}}Response(ctx *fiber.Ctx) error + } + + {{range .Responses}} + {{$statusCode := .StatusCode -}} + {{$hasHeaders := ne 0 (len .Headers) -}} + {{$fixedStatusCode := .HasFixedStatusCode -}} + {{$isRef := .IsRef -}} + {{$ref := .Ref | ucFirst -}} + {{$headers := .Headers -}} + + {{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}} + {{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 -}} + } + {{end}} + + func (response {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) Visit{{$opid}}Response(ctx *fiber.Ctx) error { + {{range $headers -}} + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + {{if eq .NameTag "Multipart" -}} + writer := multipart.NewWriter(ctx.Response().BodyWriter()) + {{end -}} + ctx.Response().Header.Set("Content-Type", {{if eq .NameTag "Multipart"}}writer.FormDataContentType(){{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}}) + {{if not .IsSupported -}} + if response.ContentLength != 0 { + ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} + {{if eq .NameTag "JSON" -}} + return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}) + {{else if eq .NameTag "Text" -}} + _, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}})) + return err + {{else if eq .NameTag "Formdata" -}} + if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil { + return err + } else { + _, err := ctx.Write([]byte(form.Encode())) + return err + } + {{else if eq .NameTag "Multipart" -}} + defer writer.Close() + return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer); + {{else -}} + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(ctx.Response().BodyWriter(), response.Body) + return err + {{end}}{{/* if eq .NameTag "JSON" */ -}} + } + {{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 -}} + func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(ctx *fiber.Ctx) error { + {{range $headers -}} + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} + ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}}) + return nil + } + {{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) ({{$opid | ucFirst}}ResponseObject, error) +{{end}}{{/* range . */ -}} +} diff --git a/pkg/codegen/templates/strict/strict-fiber.tmpl b/pkg/codegen/templates/strict/strict-fiber.tmpl index c6e93cb8cc..7ec8293171 100644 --- a/pkg/codegen/templates/strict/strict-fiber.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber.tmpl @@ -1,4 +1,4 @@ -type StrictHandlerFunc func(ctx *gin.Context, args interface{}) (interface{}, error) +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc @@ -14,7 +14,7 @@ type strictHandler struct { {{range .}} {{$opid := .OperationId}} // {{$opid}} operation middleware - func (sh *strictHandler) {{.OperationId}}(ctx *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) { + func (sh *strictHandler) {{.OperationId}}(ctx *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { var request {{$opid | ucFirst}}RequestObject {{range .PathParams -}} @@ -27,53 +27,38 @@ type strictHandler struct { {{end -}} {{ if .HasMaskedRequestContentTypes -}} - request.ContentType = ctx.ContentType() + request.ContentType = string(ctx.Request().Header.ContentType()) {{end -}} {{$multipleBodies := gt (len .Bodies) 1 -}} {{range .Bodies -}} - {{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}} + {{if $multipleBodies}}if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "{{.ContentType}}") { {{end}} {{if eq .NameTag "JSON" -}} var body {{$opid}}{{.NameTag}}RequestBody - if err := ctx.Bind(&body); err != nil { - ctx.Error(err) - return + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } 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 + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } 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 - } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary())) {{else if eq .NameTag "Text" -}} - data, err := io.ReadAll(ctx.Request.Body) - if err != nil { - ctx.Error(err) - return - } + data := ctx.Request().Body() body := {{$opid}}{{.NameTag}}RequestBody(data) request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body {{else -}} - request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = bytes.NewReader(ctx.Request().Body()) {{end}}{{/* if eq .NameTag "JSON" */ -}} {{if $multipleBodies}}}{{end}} {{end}}{{/* range .Bodies */}} - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject)) + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.{{.OperationId}}(ctx.UserContext(), request.({{$opid | ucFirst}}RequestObject)) } for _, middleware := range sh.middlewares { handler = middleware(handler, "{{.OperationId}}") @@ -82,13 +67,14 @@ type strictHandler struct { response, err := handler(ctx, request) if err != nil { - ctx.Error(err) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { - if err := validResponse.Visit{{$opid}}Response(ctx.Writer); err != nil { - ctx.Error(err) + if err := validResponse.Visit{{$opid}}Response(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } } else if response != nil { - ctx.Error(fmt.Errorf("Unexpected response type: %T", response)) + return fmt.Errorf("Unexpected response type: %T", response) } + return nil } {{end}} From 8ae6349fef1f403cd38a07e0c9398eb96920495f Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 27 Oct 2022 13:26:58 +0100 Subject: [PATCH 10/42] tests --- pkg/codegen/utils_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go index 0da9dfbedb..67765264cc 100644 --- a/pkg/codegen/utils_test.go +++ b/pkg/codegen/utils_test.go @@ -280,6 +280,23 @@ func TestSwaggerUriToGorillaUri(t *testing.T) { // TODO assert.Equal(t, "/path/{arg}/foo", SwaggerUriToGorillaUri("/path/{?arg*}/foo")) } +func TestSwaggerUriToFiberUri(t *testing.T) { + assert.Equal(t, "/path", SwaggerUriToFiberUri("/path")) + assert.Equal(t, "/path/:arg", SwaggerUriToFiberUri("/path/{arg}")) + assert.Equal(t, "/path/:arg1/:arg2", SwaggerUriToFiberUri("/path/{arg1}/{arg2}")) + assert.Equal(t, "/path/:arg1/:arg2/foo", SwaggerUriToFiberUri("/path/{arg1}/{arg2}/foo")) + + // Make sure all the exploded and alternate formats match too + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{.arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{.arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{;arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{;arg*}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{?arg}/foo")) + assert.Equal(t, "/path/:arg/foo", SwaggerUriToFiberUri("/path/{?arg*}/foo")) +} + func TestOrderedParamsFromUri(t *testing.T) { result := OrderedParamsFromUri("/path/{param1}/{.param2}/{;param3*}/foo") assert.EqualValues(t, []string{"param1", "param2", "param3"}, result) From 14197168544b2715bf46b57396faec491f451643 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 27 Oct 2022 13:27:24 +0100 Subject: [PATCH 11/42] Validate middleware --- pkg/fiber-middleware/oapi_validate.go | 142 ++++++-- pkg/fiber-middleware/oapi_validate_test.go | 396 +++++++++++---------- 2 files changed, 305 insertions(+), 233 deletions(-) diff --git a/pkg/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go index 8c37a79e13..7457f4dc91 100644 --- a/pkg/fiber-middleware/oapi_validate.go +++ b/pkg/fiber-middleware/oapi_validate.go @@ -8,69 +8,112 @@ import ( "errors" "fmt" "net/http" + "os" "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/gorillamux" + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp/fasthttpadaptor" ) +const ( + ctxKeyFiberContext = "oapi-codegen/fiber-context" + ctxKeyUserData = "oapi-codegen/user-data" +) + +// OapiValidatorFromYamlFile creates a validator middleware from a YAML file path +func OapiValidatorFromYamlFile(path string) (fiber.Handler, error) { + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading %s: %s", path, err) + } + + swagger, err := openapi3.NewLoader().LoadFromData(data) + if err != nil { + return nil, fmt.Errorf("error parsing %s as Swagger YAML: %s", + path, err) + } + + return OapiRequestValidator(swagger), nil +} + +// OapiRequestValidator is a fiber middleware function which validates incoming HTTP requests +// to make sure that they conform to the given OAPI 3.0 specification. When +// OAPI validation fails on the request, we return an HTTP/400 with error message +func OapiRequestValidator(swagger *openapi3.T) fiber.Handler { + return OapiRequestValidatorWithOptions(swagger, nil) +} + // ErrorHandler is called when there is an error in validation -type ErrorHandler func(w http.ResponseWriter, message string, statusCode int) +type ErrorHandler func(c *fiber.Ctx, message string, statusCode int) // MultiErrorHandler is called when oapi returns a MultiError type -type MultiErrorHandler func(openapi3.MultiError) (int, error) +type MultiErrorHandler func(openapi3.MultiError) error -// Options to customize request validation, openapi3filter specified options will be passed through. +// Options to customize request validation. These are passed through to +// openapi3filter. type Options struct { Options openapi3filter.Options ErrorHandler ErrorHandler + ParamDecoder openapi3filter.ContentParameterDecoder + UserData interface{} MultiErrorHandler MultiErrorHandler } -// OapiRequestValidator Creates middleware to validate request by swagger spec. -// This middleware is good for net/http either since go-chi is 100% compatible with net/http. -func OapiRequestValidator(swagger *openapi3.T) func(next http.Handler) http.Handler { - return OapiRequestValidatorWithOptions(swagger, nil) -} +// OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options +func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) fiber.Handler { -// OapiRequestValidatorWithOptions Creates middleware to validate request by swagger spec. -// This middleware is good for net/http either since go-chi is 100% compatible with net/http. -func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) func(next http.Handler) http.Handler { router, err := gorillamux.NewRouter(swagger) if err != nil { panic(err) } - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // validate request - if statusCode, err := validateRequest(r, router, options); err != nil { - if options != nil && options.ErrorHandler != nil { - options.ErrorHandler(w, err.Error(), statusCode) - } else { - http.Error(w, err.Error(), statusCode) - } - return + return func(c *fiber.Ctx) error { + + err := ValidateRequestFromContext(c, router, options) + if err != nil { + if options != nil && options.ErrorHandler != nil { + options.ErrorHandler(c, err.Error(), http.StatusBadRequest) + // in case the handler didn't internally call Abort, stop the chain + return nil + } else { + // note: I am not sure if this is the best way to handle this + return fiber.NewError(http.StatusBadRequest, err.Error()) } - - // serve - next.ServeHTTP(w, r) - }) + } + return c.Next() } - } -// This function is called from the middleware above and actually does the work +// ValidateRequestFromContext is called from the middleware above and actually does the work // of validating a request. -func validateRequest(r *http.Request, router routers.Router, options *Options) (int, error) { +func ValidateRequestFromContext(c *fiber.Ctx, router routers.Router, options *Options) error { + + r := &http.Request{} + + err := fasthttpadaptor.ConvertRequest(c.Context(), r, false) + if err != nil { + return err + } - // Find route route, pathParams, err := router.FindRoute(r) + + // We failed to find a matching route for the request. if err != nil { - return http.StatusBadRequest, err // We failed to find a matching route for the request. + switch e := err.(type) { + case *routers.RouteError: + // We've got a bad request, the path requested doesn't match + // either server, or path, or something. + return errors.New(e.Reason) + default: + // This should never happen today, but if our upstream code changes, + // we don't want to crash the server, so handle the unexpected error. + return fmt.Errorf("error validating route: %s", err.Error()) + } } // Validate request @@ -80,11 +123,18 @@ func validateRequest(r *http.Request, router routers.Router, options *Options) ( Route: route, } + // Pass the gin context into the request validator, so that any callbacks + // which it invokes make it available. + requestContext := context.WithValue(context.Background(), ctxKeyFiberContext, c) + if options != nil { requestValidationInput.Options = &options.Options + requestValidationInput.ParamDecoder = options.ParamDecoder + requestContext = context.WithValue(requestContext, ctxKeyUserData, options.UserData) } - if err := openapi3filter.ValidateRequest(context.Background(), requestValidationInput); err != nil { + err = openapi3filter.ValidateRequest(requestContext, requestValidationInput) + if err != nil { me := openapi3.MultiError{} if errors.As(err, &me) { errFunc := getMultiErrorHandlerFromOptions(options) @@ -97,20 +147,34 @@ func validateRequest(r *http.Request, router routers.Router, options *Options) ( // Split up the verbose error by lines and return the first one // openapi errors seem to be multi-line with a decent message on the first errorLines := strings.Split(e.Error(), "\n") - return http.StatusBadRequest, fmt.Errorf(errorLines[0]) + return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0]) case *openapi3filter.SecurityRequirementsError: - return http.StatusUnauthorized, err + return fmt.Errorf("error in openapi3filter.SecurityRequirementsError: %s", e.Error()) default: // This should never happen today, but if our upstream code changes, // we don't want to crash the server, so handle the unexpected error. - return http.StatusInternalServerError, fmt.Errorf("error validating route: %s", err.Error()) + return fmt.Errorf("error validating request: %s", err) } } + return nil +} + +// GetFiberContext gets the fiber context from within requests. It returns +// nil if not found or wrong type. +func GetFiberContext(c context.Context) *fiber.Ctx { + iface := c.Value(ctxKeyFiberContext) + if iface == nil { + return nil + } - return http.StatusOK, nil + fiberCtx, ok := iface.(*fiber.Ctx) + if ok { + return fiberCtx + } + return nil } -// attempt to get the MultiErrorHandler from the options. If it is not set, +// getMultiErrorHandlerFromOptions attempts to get the MultiErrorHandler from the options. If it is not set, // return a default handler func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { if options == nil { @@ -127,6 +191,6 @@ func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { // defaultMultiErrorHandler returns a StatusBadRequest (400) and a list // of all the errors. This method is called if there are no other // methods defined on the options. -func defaultMultiErrorHandler(me openapi3.MultiError) (int, error) { - return http.StatusBadRequest, me +func defaultMultiErrorHandler(me openapi3.MultiError) error { + return fmt.Errorf("multiple errors encountered: %s", me) } diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 7a90226518..d392ab9e0c 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -1,16 +1,18 @@ package middleware import ( + "bytes" "context" _ "embed" + "encoding/json" "errors" + "fmt" "io" "net/http" "net/http/httptest" "net/url" "testing" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/gofiber/fiber/v2" @@ -21,144 +23,195 @@ import ( //go:embed test_spec.yaml var testSchema []byte -func doGet(t *testing.T, mux *fiber.App, rawURL string) *httptest.ResponseRecorder { +func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { u, err := url.Parse(rawURL) if err != nil { t.Fatalf("Invalid url: %s", rawURL) } - response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, mux) - return response.Recorder + req := httptest.NewRequest("GET", u.RequestURI(), nil) + req.Header.Add("Accept", "application/json") + req.Host = u.Host + + return app.Test(req) } -func doPost(t *testing.T, mux *fiber.App, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { +func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) (*http.Response, error) { u, err := url.Parse(rawURL) if err != nil { t.Fatalf("Invalid url: %s", rawURL) } - response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, mux) - return response.Recorder + buf, err := json.Marshal(jsonBody) + if err != nil { + return nil, err + } + req := httptest.NewRequest("POST", u.RequestURI(), bytes.NewReader(buf)) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Host = u.Host + + return app.Test(req) } func TestOapiRequestValidator(t *testing.T) { - swagger, err := openapi3.NewLoader().LoadFromData(testSchema) - require.NoError(t, err, "Error initializing swagger") - - r := fiber.New() - // register middleware - r.Use(OapiRequestValidator(swagger)) - - // basic cases - testRequestValidatorBasicFunctions(t, r) -} - -func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { swagger, err := openapi3.NewLoader().LoadFromData(testSchema) require.NoError(t, err, "Error initializing swagger") - r := fiber.New() + // Create a new fiber router + app := fiber.New() // Set up an authenticator to check authenticated function. It will allow // access to "someScope", but disallow others. options := Options{ + ErrorHandler: func(c *fiber.Ctx, message string, statusCode int) { + c.Status(statusCode).SendString("test: " + message) + }, Options: openapi3filter.Options{ - ExcludeRequestBody: false, - ExcludeResponseBody: false, - IncludeResponseStatus: true, - MultiError: true, + AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { + // The fiber context should be propagated into here. + gCtx := GetFiberContext(c) + assert.NotNil(t, gCtx) + // As should user data + assert.EqualValues(t, "hi!", c.Value(ctxKeyUserData)) + + for _, s := range input.Scopes { + if s == "someScope" { + return nil + } + if s == "unauthorized" { + return errors.New("unauthorized") + } + } + return errors.New("forbidden") + }, }, + UserData: "hi!", } - // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) + // Install our OpenApi based request validator + app.Use(OapiRequestValidatorWithOptions(swagger, &options)) called := false // Install a request handler for /resource. We want to make sure it doesn't // get called. - r.Get("/multiparamresource", func(c *fiber.Ctx) error { + app.Get("/resource", func(c *fiber.Ctx) error { called = true return nil }) + // Let's send the request to the wrong server, this should fail validation + { + res, _ := doGet(t, app, "https://not.deepmap.ai/resource") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.False(t, called, "Handler should not have been called") + } // Let's send a good request, it should pass { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) + res, _ := doGet(t, app, "https://deepmap.ai/resource") + assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false } - // Let's send a request with a missing parameter, it should return - // a bad status + // Send an out-of-spec parameter { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } + res, _ := doGet(t, app, "https://deepmap.ai/resource?id=500") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false } - // Let's send a request with 2 missing parameters, it should return - // a bad status + // Send a bad parameter type { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value is required but missing") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") - } + res, _ := doGet(t, app, "https://deepmap.ai/resource?id=foo") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false } - // Let's send a request with a 1 missing parameter, and another outside - // or the parameters. It should return a bad status + // Add a handler for the POST message + app.Post("/resource", func(c *fiber.Ctx) error { + called = true + return c.SendStatus(http.StatusNoContent) + }) + + called = false + // Send a good request body { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) - if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "number must be at most 100") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "value is required but missing") + body := struct { + Name string `json:"name"` + }{ + Name: "Marcin", } + res, _ := doPost(t, app, "https://deepmap.ai/resource", body) + assert.Equal(t, http.StatusNoContent, res.StatusCode) + assert.True(t, called, "Handler should have been called") + called = false + } + + // Send a malformed body + { + body := struct { + Name int `json:"name"` + }{ + Name: 7, + } + res, _ := doPost(t, app, "https://deepmap.ai/resource", body) + assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false } - // Let's send a request with a parameters that do not meet spec. It should - // return a bad status + app.Get("/protected_resource", func(c *fiber.Ctx) error { + called = true + return c.SendStatus(http.StatusNoContent) + }) + + // Call a protected function to which we have access { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusBadRequest, rec.Code) - body, err := io.ReadAll(rec.Body) + res, _ := doGet(t, app, "https://deepmap.ai/protected_resource") + assert.Equal(t, http.StatusNoContent, res.StatusCode) + assert.True(t, called, "Handler should have been called") + called = false + } + + app.Get("/protected_resource2", func(c *fiber.Ctx) error { + called = true + return c.SendStatus(http.StatusNoContent) + }) + // Call a protected function to which we don't have access + { + res, _ := doGet(t, app, "https://deepmap.ai/protected_resource2") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.False(t, called, "Handler should not have been called") + called = false + } + + app.Get("/protected_resource_401", func(c *fiber.Ctx) error { + called = true + return c.SendStatus(http.StatusNoContent) + }) + // Call a protected function without credentials + { + res, _ := doGet(t, app, "https://deepmap.ai/protected_resource_401") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { - assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") - assert.Contains(t, string(body), "parameter \"id2\"") - assert.Contains(t, string(body), "number must be at least 10") + assert.Equal(t, "test: error in openapi3filter.SecurityRequirementsError: Security requirements failed", string(body)) } assert.False(t, called, "Handler should not have been called") called = false } } -func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { +func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { swagger, err := openapi3.NewLoader().LoadFromData(testSchema) require.NoError(t, err, "Error initializing swagger") - r := fiber.New() + app := fiber.New() // Set up an authenticator to check authenticated function. It will allow // access to "someScope", but disallow others. @@ -169,27 +222,24 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) IncludeResponseStatus: true, MultiError: true, }, - MultiErrorHandler: func(me openapi3.MultiError) (int, error) { - return http.StatusTeapot, me - }, } // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) + app.Use(OapiRequestValidatorWithOptions(swagger, &options)) called := false // Install a request handler for /resource. We want to make sure it doesn't // get called. - r.Get("/multiparamresource", func(c *fiber.Ctx) error { + app.Get("/multiparamresource", func(c *fiber.Ctx) error { called = true return nil }) // Let's send a good request, it should pass { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50&id2=50") - assert.Equal(t, http.StatusOK, rec.Code) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") + assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false } @@ -197,10 +247,11 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a missing parameter, it should return // a bad status { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=50") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { + assert.Contains(t, string(body), "multiple errors encountered") assert.Contains(t, string(body), "parameter \"id2\"") assert.Contains(t, string(body), "value is required but missing") } @@ -211,10 +262,11 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a 2 missing parameters, it should return // a bad status { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { + assert.Contains(t, string(body), "multiple errors encountered") assert.Contains(t, string(body), "parameter \"id\"") assert.Contains(t, string(body), "value is required but missing") assert.Contains(t, string(body), "parameter \"id2\"") @@ -227,10 +279,11 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a 1 missing parameter, and another outside // or the parameters. It should return a bad status { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=500") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { + assert.Contains(t, string(body), "multiple errors encountered") assert.Contains(t, string(body), "parameter \"id\"") assert.Contains(t, string(body), "number must be at most 100") assert.Contains(t, string(body), "parameter \"id2\"") @@ -243,12 +296,13 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a parameters that do not meet spec. It should // return a bad status { - rec := doGet(t, r, "http://deepmap.ai/multiparamresource?id=abc&id2=1") - assert.Equal(t, http.StatusTeapot, rec.Code) - body, err := io.ReadAll(rec.Body) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { + assert.Contains(t, string(body), "multiple errors encountered") assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") + assert.Contains(t, string(body), "parsing \"abc\": invalid syntax") assert.Contains(t, string(body), "parameter \"id2\"") assert.Contains(t, string(body), "number must be at least 10") } @@ -257,154 +311,108 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) } } -func TestOapiRequestValidatorWithOptions(t *testing.T) { +func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) { swagger, err := openapi3.NewLoader().LoadFromData(testSchema) require.NoError(t, err, "Error initializing swagger") - r := fiber.New() + app := fiber.New() // Set up an authenticator to check authenticated function. It will allow // access to "someScope", but disallow others. options := Options{ - ErrorHandler: func(w http.ResponseWriter, message string, statusCode int) { - http.Error(w, "test: "+message, statusCode) - }, Options: openapi3filter.Options{ - AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { - - for _, s := range input.Scopes { - if s == "someScope" { - return nil - } - } - return errors.New("unauthorized") - }, + ExcludeRequestBody: false, + ExcludeResponseBody: false, + IncludeResponseStatus: true, + MultiError: true, + }, + MultiErrorHandler: func(me openapi3.MultiError) error { + return fmt.Errorf("Bad stuff - %s", me.Error()) }, } // register middleware - r.Use(OapiRequestValidatorWithOptions(swagger, &options)) - - // basic cases - testRequestValidatorBasicFunctions(t, r) - - called := false - - r.Get("/protected_resource", func(c *fiber.Ctx) error { - called = true - c.Status(http.StatusNoContent) - return nil - }) - - // Call a protected function to which we have access - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource") - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") - called = false - } - - r.Get("/protected_resource2", func(c *fiber.Ctx) error { - called = true - c.Status(http.StatusNoContent) - return nil - }) - // Call a protected function to which we dont have access - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource2") - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.False(t, called, "Handler should not have been called") - called = false - } - - r.Get("/protected_resource_401", func(c *fiber.Ctx) error { - called = true - c.Status(http.StatusNoContent) - return nil - }) - // Call a protected function without credentials - { - rec := doGet(t, r, "http://deepmap.ai/protected_resource_401") - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.Equal(t, "test: Security requirements failed\n", rec.Body.String()) - assert.False(t, called, "Handler should not have been called") - called = false - } - -} - -func testRequestValidatorBasicFunctions(t *testing.T, r *fiber.App) { + app.Use(OapiRequestValidatorWithOptions(swagger, &options)) called := false // Install a request handler for /resource. We want to make sure it doesn't // get called. - r.Get("/resource", func(*fiber.Ctx) error { + app.Get("/multiparamresource", func(c *fiber.Ctx) error { called = true return nil }) - // Let's send the request to the wrong server, this should fail validation - { - rec := doGet(t, r, "http://not.deepmap.ai/resource") - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.False(t, called, "Handler should not have been called") - } - // Let's send a good request, it should pass { - rec := doGet(t, r, "http://deepmap.ai/resource") - assert.Equal(t, http.StatusOK, rec.Code) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") + assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false } - // Send an out-of-spec parameter + // Let's send a request with a missing parameter, it should return + // a bad status { - rec := doGet(t, r, "http://deepmap.ai/resource?id=500") - assert.Equal(t, http.StatusBadRequest, rec.Code) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "Bad stuff") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } assert.False(t, called, "Handler should not have been called") called = false } - // Send a bad parameter type + // Let's send a request with a 2 missing parameters, it should return + // a bad status { - rec := doGet(t, r, "http://deepmap.ai/resource?id=foo") - assert.Equal(t, http.StatusBadRequest, rec.Code) + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "Bad stuff") + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "value is required but missing") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") + } assert.False(t, called, "Handler should not have been called") called = false } - // Add a handler for the POST message - r.Post("/resource", func(c *fiber.Ctx) error { - called = true - c.Status(http.StatusNoContent) - return nil - }) - - called = false - // Send a good request body + // Let's send a request with a 1 missing parameter, and another outside + // or the parameters. It should return a bad status { - body := struct { - Name string `json:"name"` - }{ - Name: "Marcin", + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "Bad stuff") + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "number must be at most 100") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "value is required but missing") } - rec := doPost(t, r, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusNoContent, rec.Code) - assert.True(t, called, "Handler should have been called") + assert.False(t, called, "Handler should not have been called") called = false } - // Send a malformed body + // Let's send a request with a parameters that do not meet spec. It should + // return a bad status { - body := struct { - Name int `json:"name"` - }{ - Name: 7, + res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + body, err := io.ReadAll(res.Body) + if assert.NoError(t, err) { + assert.Contains(t, string(body), "Bad stuff") + assert.Contains(t, string(body), "parameter \"id\"") + assert.Contains(t, string(body), "parsing \"abc\": invalid syntax") + assert.Contains(t, string(body), "parameter \"id2\"") + assert.Contains(t, string(body), "number must be at least 10") } - rec := doPost(t, r, "http://deepmap.ai/resource", body) - assert.Equal(t, http.StatusBadRequest, rec.Code) assert.False(t, called, "Handler should not have been called") called = false } From abe8eca11927fbb8930d1ff958bcecee1adc3108 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 27 Oct 2022 13:29:45 +0100 Subject: [PATCH 12/42] Templates --- .../templates/fiber/fiber-handler.tmpl | 56 ++---- .../templates/fiber/fiber-middleware.tmpl | 160 +++++------------- 2 files changed, 56 insertions(+), 160 deletions(-) diff --git a/pkg/codegen/templates/fiber/fiber-handler.tmpl b/pkg/codegen/templates/fiber/fiber-handler.tmpl index 0a2f059146..4a55b3c512 100644 --- a/pkg/codegen/templates/fiber/fiber-handler.tmpl +++ b/pkg/codegen/templates/fiber/fiber-handler.tmpl @@ -1,57 +1,25 @@ -// Handler creates fiber.Handler with routing matching OpenAPI spec. -func Handler(si ServerInterface) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions{}) -} - +// FiberServerOptions provides options for the Fiber server. type FiberServerOptions struct { BaseURL string - BaseRouter *fiber.App - Middlewares []fiber.Handler - ErrorHandlerFunc func(c *fiber.Ctx, err error) error -} - -// HandlerFromMux creates fiber.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, r *fiber.App) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions { - BaseRouter: r, - }) + Middlewares []MiddlewareFunc } -func HandlerFromMuxWithBaseURL(si ServerInterface, r *fiber.App, baseURL string) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions { - BaseURL: baseURL, - BaseRouter: r, - }) -} - -// HandlerWithOptions creates fiber.Handler with additional options -func HandlerWithOptions(si ServerInterface, options FiberServerOptions) fiber.Handler { -r := options.BaseRouter - -if r == nil { -r = fiber.New() -} - -if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) error { - - if err != nil { - c.Status(http.StatusBadRequest) - return c.SendString(err.Error()) - } - - return c.SendStatus(http.StatusBadRequest) - } +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) } +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { {{if .}}wrapper := ServerInterfaceWrapper{ Handler: si, -HandlerMiddlewares: options.Middlewares, -ErrorHandlerFunc: options.ErrorHandlerFunc, +} + +for _, m := range options.Middlewares { + router.Use(m) } {{end}} {{range .}} -r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) +router.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) {{end}} -return r } diff --git a/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/pkg/codegen/templates/fiber/fiber-middleware.tmpl index d28e2a6c13..4ea9f21357 100644 --- a/pkg/codegen/templates/fiber/fiber-middleware.tmpl +++ b/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -1,15 +1,15 @@ // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface - HandlerMiddlewares []fiber.Handler - ErrorHandlerFunc func(c *fiber.Ctx, err error) error } +type MiddlewareFunc fiber.Handler + {{range .}}{{$opid := .OperationId}} // {{$opid}} operation middleware -func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { - ctx := r.Context() +func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} var err error {{end}} @@ -18,37 +18,44 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} {{if .IsPassThrough}} - {{$varName}} = c.Params("{{.ParamName}}") + {{$varName}} = c.Query("{{.ParamName}}") {{end}} {{if .IsJson}} - err = json.Unmarshal([]byte(c.Params("{{.ParamName}}")), &{{$varName}}) + err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}}) if err != nil { - return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, c.Params("{{.ParamName}}"), &{{$varName}}) + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", c.Params("{{.ParamName}}"), &{{$varName}}) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) } {{end}} {{end}} {{range .SecurityDefinitions}} - ctx = context.WithValue(ctx, {{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}}) + c.Context().SetUserValue({{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}}) {{end}} {{if .RequiresParamObject}} // Parameter object where we will unmarshal all parameters from the context var params {{.OperationId}}Params + {{if .QueryParams}} + query, err := url.ParseQuery(string(c.Request().URI().QueryString())) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) + } + {{end}} + {{range $paramIdx, $param := .QueryParams}} {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- {{ end }} {{ if (or (or .Required .IsPassThrough) .IsJson) }} - if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + if paramValue := c.Query("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue @@ -58,83 +65,80 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} }{{if .Required}} else { - return siw.ErrorHandlerFunc(c, &RequiredParamError{ParamName: "{{.ParamName}}"}) + err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found") + c.Status(fiber.StatusBadRequest).JSON(err) + return err }{{end}} {{end}} {{if .IsStyled}} - err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", query, ¶ms.{{.GoName}}) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) } {{end}} {{end}} {{if .HeaderParams}} - headers := r.Header + headers := c.GetReqHeaders() {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + if value, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { var {{.GoName}} {{.TypeDef}} - n := len(valueList) - if n != 1 { - return siw.ErrorHandlerFunc(c, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n}) - } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} {{if .IsJson}} - err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + err = json.Unmarshal([]byte(value), &{{.GoName}}) if err != nil { - return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } {{end}} {{if .IsStyled}} - err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, value, &{{.GoName}}) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) } {{end}} params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} } {{if .Required}}else { - err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") - return siw.ErrorHandlerFunc(c, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err}) + err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %w", err) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) }{{end}} {{end}} {{end}} {{range .CookieParams}} - var cookie *http.Cookie + var cookie string - if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { + if cookie = c.Cookies("{{.ParamName}}"); cookie == "" { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie {{end}} {{- if .IsJson}} var value {{.TypeDef}} var decoded string - decoded, err := url.QueryUnescape(cookie.Value) + decoded, err := url.QueryUnescape(cookie) if err != nil { - err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'") - return siw.ErrorHandlerFunc(c, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}': %w", err).Error()) } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - return siw.ErrorHandlerFunc(c, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } params.{{.GoName}} = {{if not .Required}}&{{end}}value @@ -142,9 +146,9 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { {{- if .IsStyled}} var value {{.TypeDef}} - err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie, &value) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} @@ -152,89 +156,13 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx)error { } {{- if .Required}} else { - return siw.ErrorHandlerFunc(c, &RequiredParamError{ParamName: "{{.ParamName}}"}) + err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found") + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } {{- end}} {{end}} {{end}} - var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error{ - return siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(c, r.WithContext(ctx)) + return siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) } {{end}} - -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 UnmarshallingParamError struct { - ParamName string - Err error -} - -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) -} - -func (e *UnmarshallingParamError) 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) -} From a8ea99404a55b601732e97c5e24339e5c375e703 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Fri, 28 Oct 2022 13:30:11 +0100 Subject: [PATCH 13/42] Update petstore --- examples/petstore-expanded/fiber/petstore.go | 20 ++- .../petstore-expanded/fiber/petstore_test.go | 122 ++++++++++-------- 2 files changed, 81 insertions(+), 61 deletions(-) diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go index 612faae2a9..0277f93c08 100644 --- a/examples/petstore-expanded/fiber/petstore.go +++ b/examples/petstore-expanded/fiber/petstore.go @@ -18,8 +18,20 @@ import ( func main() { var port = flag.Int("port", 8080, "Port for test HTTP server") + flag.Parse() + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + s := NewFiberPetServer(petStore) + + // And we serve HTTP until the world ends. + log.Fatal(s.Listen(fmt.Sprintf("localhost:%d", *port))) +} + +func NewFiberPetServer(petStore *api.PetStore) *fiber.App { + swagger, err := api.GetSwagger() if err != nil { fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) @@ -30,9 +42,6 @@ func main() { // 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() - // This is how you set up a basic fiber router app := fiber.New() @@ -41,8 +50,7 @@ func main() { app.Use(middleware.OapiRequestValidator(swagger)) // We now register our petStore above as the handler for the interface - api.HandlerFromMux(petStore, app) + api.RegisterHandlers(app, petStore) - // And we serve HTTP until the world ends. - log.Fatal(app.Listen(fmt.Sprintf("0.0.0.0:%d", *port))) + return app } diff --git a/examples/petstore-expanded/fiber/petstore_test.go b/examples/petstore-expanded/fiber/petstore_test.go index 310443bacb..ed900c3dcc 100644 --- a/examples/petstore-expanded/fiber/petstore_test.go +++ b/examples/petstore-expanded/fiber/petstore_test.go @@ -1,46 +1,66 @@ package main import ( + "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" + "net/url" "testing" "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" - middleware "github.com/deepmap/oapi-codegen/pkg/fiber-middleware" - "github.com/deepmap/oapi-codegen/pkg/testutil" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func doGet(t *testing.T, mux *fiber.App, url string) *httptest.ResponseRecorder { - response := testutil.NewRequest().Get(url).WithAcceptJson().GoWithHTTPHandler(t, mux) - return response.Recorder -} +func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { -func TestPetStore(t *testing.T) { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } - var err error + req := httptest.NewRequest("GET", u.RequestURI(), nil) + req.Header.Add("Accept", "application/json") + req.Host = u.Host - // Get the swagger description of our API - swagger, err := api.GetSwagger() - require.NoError(t, err) + return app.Test(req) +} - // 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 +func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) (*http.Response, error) { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + buf, err := json.Marshal(jsonBody) + if err != nil { + return nil, err + } + req := httptest.NewRequest("POST", u.RequestURI(), bytes.NewReader(buf)) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Host = u.Host + return app.Test(req) +} - // This is how you set up a basic fiber router - r := fiber.New() +func doDelete(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } - // Use our validation middleware to check all requests against the - // OpenAPI schema. - r.Use(middleware.OapiRequestValidator(swagger)) + req := httptest.NewRequest("DELETE", u.RequestURI(), nil) + req.Header.Add("Accept", "application/json") + req.Host = u.Host + return app.Test(req) +} +func TestPetStore(t *testing.T) { + var err error store := api.NewPetStore() - api.HandlerFromMux(store, r) + fiberPetServer := NewFiberPetServer(store) t.Run("Add pet", func(t *testing.T) { tag := "TagOfSpot" @@ -49,12 +69,12 @@ func TestPetStore(t *testing.T) { Tag: &tag, } - rr := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, r).Recorder - assert.Equal(t, http.StatusCreated, rr.Code) + rr, _ := doPost(t, fiberPetServer, "/pets", newPet) + assert.Equal(t, http.StatusCreated, rr.StatusCode) var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshalling response") + assert.NoError(t, err, "error unmarshaling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -65,7 +85,8 @@ func TestPetStore(t *testing.T) { } store.Pets[pet.Id] = pet - rr := doGet(t, r, fmt.Sprintf("/pets/%d", pet.Id)) + rr, _ := doGet(t, fiberPetServer, fmt.Sprintf("/pets/%d", pet.Id)) + assert.Equal(t, http.StatusOK, rr.StatusCode) var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) @@ -74,8 +95,8 @@ func TestPetStore(t *testing.T) { }) t.Run("Pet not found", func(t *testing.T) { - rr := doGet(t, r, "/pets/27179095781") - assert.Equal(t, http.StatusNotFound, rr.Code) + rr, _ := doGet(t, fiberPetServer, "/pets/27179095781") + assert.Equal(t, http.StatusNotFound, rr.StatusCode) var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) @@ -84,14 +105,11 @@ func TestPetStore(t *testing.T) { }) t.Run("List all pets", func(t *testing.T) { - store.Pets = map[int64]api.Pet{ - 1: {}, - 2: {}, - } + store.Pets = map[int64]api.Pet{1: {}, 2: {}} // Now, list all pets, we should have two - rr := doGet(t, r, "/pets") - assert.Equal(t, http.StatusOK, rr.Code) + rr, _ := doGet(t, fiberPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.StatusCode) var petList []api.Pet err = json.NewDecoder(rr.Body).Decode(&petList) @@ -110,8 +128,8 @@ func TestPetStore(t *testing.T) { } // Filter pets by tag, we should have 1 - rr := doGet(t, r, "/pets?tags=TagOfFido") - assert.Equal(t, http.StatusOK, rr.Code) + rr, _ := doGet(t, fiberPetServer, "/pets?tags=TagOfFido") + assert.Equal(t, http.StatusOK, rr.StatusCode) var petList []api.Pet err = json.NewDecoder(rr.Body).Decode(&petList) @@ -120,14 +138,11 @@ func TestPetStore(t *testing.T) { }) t.Run("Filter pets by tag", func(t *testing.T) { - store.Pets = map[int64]api.Pet{ - 1: {}, - 2: {}, - } + store.Pets = map[int64]api.Pet{1: {}, 2: {}} - // Filter pets by non-existent tag, we should have 0 - rr := doGet(t, r, "/pets?tags=NotExists") - assert.Equal(t, http.StatusOK, rr.Code) + // Filter pets by non existent tag, we should have 0 + rr, _ := doGet(t, fiberPetServer, "/pets?tags=NotExists") + assert.Equal(t, http.StatusOK, rr.StatusCode) var petList []api.Pet err = json.NewDecoder(rr.Body).Decode(&petList) @@ -136,31 +151,28 @@ func TestPetStore(t *testing.T) { }) t.Run("Delete pets", func(t *testing.T) { - store.Pets = map[int64]api.Pet{ - 1: {}, - 2: {}, - } + store.Pets = map[int64]api.Pet{1: {}, 2: {}} // Let's delete non-existent pet - rr := testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, r).Recorder - assert.Equal(t, http.StatusNotFound, rr.Code) + rr, _ := doDelete(t, fiberPetServer, "/pets/7") + assert.Equal(t, http.StatusNotFound, rr.StatusCode) var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshalling 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, _ = doDelete(t, fiberPetServer, "/pets/1") + assert.Equal(t, http.StatusNoContent, rr.StatusCode) - rr = testutil.NewRequest().Delete("/pets/2").GoWithHTTPHandler(t, r).Recorder - assert.Equal(t, http.StatusNoContent, rr.Code) + rr, _ = doDelete(t, fiberPetServer, "/pets/2") + assert.Equal(t, http.StatusNoContent, rr.StatusCode) // Should have no pets left. var petList []api.Pet - rr = doGet(t, r, "/pets") - assert.Equal(t, http.StatusOK, rr.Code) + rr, _ = doGet(t, fiberPetServer, "/pets") + assert.Equal(t, http.StatusOK, rr.StatusCode) err = json.NewDecoder(rr.Body).Decode(&petList) assert.NoError(t, err, "error getting response", err) assert.Equal(t, 0, len(petList)) From 3d2d00d93f7ab911d4f8b606b8d590a20d282cf2 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Fri, 28 Oct 2022 13:30:23 +0100 Subject: [PATCH 14/42] Generate --- ...petstore.gen.go => petstore-server.gen.go} | 247 +++--------------- .../fiber/api/petstore-types.gen.go | 46 ++++ 2 files changed, 82 insertions(+), 211 deletions(-) rename examples/petstore-expanded/fiber/api/{petstore.gen.go => petstore-server.gen.go} (52%) create mode 100644 examples/petstore-expanded/fiber/api/petstore-types.gen.go diff --git a/examples/petstore-expanded/fiber/api/petstore.gen.go b/examples/petstore-expanded/fiber/api/petstore-server.gen.go similarity index 52% rename from examples/petstore-expanded/fiber/api/petstore.gen.go rename to examples/petstore-expanded/fiber/api/petstore-server.gen.go index 82475bd4fa..3c691f35b3 100644 --- a/examples/petstore-expanded/fiber/api/petstore.gen.go +++ b/examples/petstore-expanded/fiber/api/petstore-server.gen.go @@ -8,7 +8,6 @@ import ( "compress/gzip" "encoding/base64" "fmt" - "net/http" "net/url" "path" "strings" @@ -18,48 +17,6 @@ import ( "github.com/gofiber/fiber/v2" ) -// Error defines model for Error. -type Error struct { - // Code Error code - Code int32 `json:"code"` - - // Message Error message - Message string `json:"message"` -} - -// NewPet defines model for NewPet. -type NewPet struct { - // Name Name of the pet - Name string `json:"name"` - - // Tag Type of the pet - Tag *string `json:"tag,omitempty"` -} - -// Pet defines model for Pet. -type Pet struct { - // Id Unique id of the pet - Id int64 `json:"id"` - - // Name Name of the pet - Name string `json:"name"` - - // Tag Type of the pet - Tag *string `json:"tag,omitempty"` -} - -// FindPetsParams defines parameters for FindPets. -type FindPetsParams struct { - // Tags tags to filter by - Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` - - // Limit 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 - // ServerInterface represents all server handlers. type ServerInterface interface { // Returns all pets @@ -78,240 +35,108 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []fiber.Handler - ErrorHandlerFunc func(c *fiber.Ctx, err error) error + Handler ServerInterface } +type MiddlewareFunc fiber.Handler + // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) error { - ctx := r.Context() var err error // Parameter object where we will unmarshal all parameters from the context var params FindPetsParams - // ------------- Optional query parameter "tags" ------------- - - err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), ¶ms.Tags) + query, err := url.ParseQuery(string(c.Request().URI().QueryString())) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "tags", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) } - // ------------- Optional query parameter "limit" ------------- + // ------------- Optional query parameter "tags" ------------- - err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), ¶ms.Limit) + err = runtime.BindQueryParameter("form", true, false, "tags", query, ¶ms.Tags) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter tags: %w", err).Error()) } - var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { - return siw.Handler.FindPets(c, params) - }) + // ------------- Optional query parameter "limit" ------------- - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) + err = runtime.BindQueryParameter("form", true, false, "limit", query, ¶ms.Limit) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter limit: %w", err).Error()) } - handler.ServeHTTP(c, r.WithContext(ctx)) + return siw.Handler.FindPets(c, params) } // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(c *fiber.Ctx) error { - ctx := r.Context() - - var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { - return siw.Handler.AddPet(c) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - handler.ServeHTTP(c, r.WithContext(ctx)) + return siw.Handler.AddPet(c) } // DeletePet operation middleware func (siw *ServerInterfaceWrapper) DeletePet(c *fiber.Ctx) error { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) + err = runtime.BindStyledParameter("simple", false, "id", c.Params("id"), &id) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "id", Err: err}) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter id: %w", err).Error()) } - var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { - return siw.Handler.DeletePet(c, id) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(c, r.WithContext(ctx)) + return siw.Handler.DeletePet(c, id) } // FindPetByID operation middleware func (siw *ServerInterfaceWrapper) FindPetByID(c *fiber.Ctx) error { - ctx := r.Context() var err error // ------------- Path parameter "id" ------------- var id int64 - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, c.Params("id"), &id) + err = runtime.BindStyledParameter("simple", false, "id", c.Params("id"), &id) if err != nil { - return siw.ErrorHandlerFunc(c, &InvalidParamFormatError{ParamName: "id", Err: err}) - } - - var handler fiber.Handler = fiber.Handler(func(c *fiber.Ctx) error { - return siw.Handler.FindPetByID(c, id) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter id: %w", err).Error()) } - handler.ServeHTTP(c, 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 UnmarshallingParamError struct { - ParamName string - Err error -} - -func (e *UnmarshallingParamError) Error() string { - return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) -} - -func (e *UnmarshallingParamError) 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 fiber.Handler with routing matching OpenAPI spec. -func Handler(si ServerInterface) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions{}) + return siw.Handler.FindPetByID(c, id) } +// FiberServerOptions provides options for the Fiber server. type FiberServerOptions struct { - BaseURL string - BaseRouter *fiber.App - Middlewares []fiber.Handler - ErrorHandlerFunc func(c *fiber.Ctx, err error) error -} - -// HandlerFromMux creates fiber.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, r *fiber.App) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions{ - BaseRouter: r, - }) + BaseURL string + Middlewares []MiddlewareFunc } -func HandlerFromMuxWithBaseURL(si ServerInterface, r *fiber.App, baseURL string) fiber.Handler { - return HandlerWithOptions(si, FiberServerOptions{ - BaseURL: baseURL, - BaseRouter: r, - }) +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router fiber.Router, si ServerInterface) { + RegisterHandlersWithOptions(router, si, FiberServerOptions{}) } -// HandlerWithOptions creates fiber.Handler with additional options -func HandlerWithOptions(si ServerInterface, options FiberServerOptions) fiber.Handler { - r := options.BaseRouter - - if r == nil { - r = fiber.New() - } - - if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(c *fiber.Ctx, err error) error { - - if err != nil { - c.Status(http.StatusBadRequest) - return c.SendString(err.Error()) - } - - return c.SendStatus(http.StatusBadRequest) - } +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) { + wrapper := ServerInterfaceWrapper{ + Handler: si, } - wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + for _, m := range options.Middlewares { + router.Use(m) } - r.Get(options.BaseURL+"/pets", wrapper.FindPets) + router.Get(options.BaseURL+"/pets", wrapper.FindPets) - r.Post(options.BaseURL+"/pets", wrapper.AddPet) + router.Post(options.BaseURL+"/pets", wrapper.AddPet) - r.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) + router.Delete(options.BaseURL+"/pets/:id", wrapper.DeletePet) - r.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) + router.Get(options.BaseURL+"/pets/:id", wrapper.FindPetByID) - return r } // Base64 encoded, gzipped, json marshaled Swagger object diff --git a/examples/petstore-expanded/fiber/api/petstore-types.gen.go b/examples/petstore-expanded/fiber/api/petstore-types.gen.go new file mode 100644 index 0000000000..16bbe200cf --- /dev/null +++ b/examples/petstore-expanded/fiber/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 { + // Code Error code + Code int32 `json:"code"` + + // Message Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Id Unique id of the pet + Id int64 `json:"id"` + + // Name Name of the pet + Name string `json:"name"` + + // Tag Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + // Tags tags to filter by + Tags *[]string `form:"tags,omitempty" json:"tags,omitempty"` + + // Limit 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 From 6f95691860f17f0f9020add129a85b0c903641d4 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sat, 29 Oct 2022 19:09:14 +0100 Subject: [PATCH 15/42] Generate --- .../issue.gen.go | 28 ------------------- .../issue.gen.go | 19 ------------- 2 files changed, 47 deletions(-) diff --git a/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go b/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go index 703c262af6..6e5994e2bf 100644 --- a/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go +++ b/internal/test/issues/issue-head-digit-of-httpheader/issue.gen.go @@ -3,37 +3,9 @@ // Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. package headdigitofhttpheader -import ( - "context" - "fmt" - "net/http" -) - type N200ResponseHeaders struct { N000Foo string } type N200Response struct { Headers N200ResponseHeaders } - -type GetFooRequestObject struct { -} - -type GetFooResponseObject interface { - VisitGetFooResponse(w http.ResponseWriter) error -} - -type GetFoo200Response = N200Response - -func (response GetFoo200Response) VisitGetFooResponse(w http.ResponseWriter) error { - w.Header().Set("000-foo", fmt.Sprint(response.Headers.N000Foo)) - w.WriteHeader(200) - return nil -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - - // (GET /foo) - GetFoo(ctx context.Context, request GetFooRequestObject) (GetFooResponseObject, error) -} diff --git a/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go index 17607466a4..4a5d3e86f6 100644 --- a/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go +++ b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go @@ -2,22 +2,3 @@ // // Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. package head_digit_of_operation_id - -import ( - "context" - "net/http" -) - -type N3GPPFooRequestObject struct { -} - -type N3GPPFooResponseObject interface { - VisitN3GPPFooResponse(w http.ResponseWriter) error -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - - // (GET /3gpp/foo) - N3GPPFoo(ctx context.Context, request N3GPPFooRequestObject) (N3GPPFooResponseObject, error) -} From 14852387b17c865770c2d17ee8599f61341f26b9 Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sat, 29 Oct 2022 19:17:44 +0100 Subject: [PATCH 16/42] Readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd123c3a66..cd93bc926c 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,9 @@ you can focus on implementing the business logic for your service. We have chosen to focus on [Echo](https://github.com/labstack/echo) as our default HTTP routing engine, due to its speed and simplicity for the generated -stubs, and [Chi](https://github.com/go-chi/chi), and [Gin](https://github.com/gin-gonic/gin) -have also been added by contributors as additional routers. We chose Echo because +stubs. [Chi](https://github.com/go-chi/chi), [Gin](https://github.com/gin-gonic/gin) +and [Fiber](https://github.com/gofiber/fiber) have also been added by contributors +as additional routers. We chose Echo because the `Context` object is a mockable interface, and it allows for some advanced testing. @@ -679,7 +680,9 @@ you can specify any combination of those. - `server`: generate the Echo server boilerplate. `server` requires the types in the same package to compile. - `chi-server`: generate the Chi server boilerplate. This code is dependent on - that produced by the `types` target. + that produced by the `types` target. +- `fiber`: generate the Fiber server boilerplate. This code is dependent + on that produced by the `types` target. - `client`: generate the client boilerplate. It, too, requires the types to be present in its package. - `spec`: embed the OpenAPI spec into the generated code as a gzipped blob. From 3e9d535b509732f6b2d47e0d0a273c19168c2fdf Mon Sep 17 00:00:00 2001 From: James Eagle Date: Sat, 29 Oct 2022 22:27:44 +0100 Subject: [PATCH 17/42] Fix tests --- pkg/fiber-middleware/oapi_validate_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index d392ab9e0c..86354d8832 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -200,7 +200,7 @@ func TestOapiRequestValidator(t *testing.T) { assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { - assert.Equal(t, "test: error in openapi3filter.SecurityRequirementsError: Security requirements failed", string(body)) + assert.Equal(t, "test: error in openapi3filter.SecurityRequirementsError: security requirements failed: unauthorized", string(body)) } assert.False(t, called, "Handler should not have been called") called = false @@ -302,7 +302,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { if assert.NoError(t, err) { assert.Contains(t, string(body), "multiple errors encountered") assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "parsing \"abc\": invalid syntax") + assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") assert.Contains(t, string(body), "parameter \"id2\"") assert.Contains(t, string(body), "number must be at least 10") } @@ -409,7 +409,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) if assert.NoError(t, err) { assert.Contains(t, string(body), "Bad stuff") assert.Contains(t, string(body), "parameter \"id\"") - assert.Contains(t, string(body), "parsing \"abc\": invalid syntax") + assert.Contains(t, string(body), "value abc: an invalid integer: invalid syntax") assert.Contains(t, string(body), "parameter \"id2\"") assert.Contains(t, string(body), "number must be at least 10") } From 49ca45ef7d280d79367eb63caad8a48fc599654c Mon Sep 17 00:00:00 2001 From: James Eagle Date: Mon, 19 Dec 2022 13:06:05 +0000 Subject: [PATCH 18/42] Fix build --- internal/test/strict-server/strict_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index 40da18ef4e..6c3d72a857 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -11,14 +11,16 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin" "github.com/go-chi/chi/v5" "github.com/gofiber/adaptor/v2" "github.com/gofiber/fiber/v2" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - + "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" + chiAPI "github.com/gin-gonic/gin" clientAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" echoAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" fiberAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/fiber" @@ -28,10 +30,10 @@ import ( ) func TestChiServer(t *testing.T) { - server := api.StrictServer{} - strictHandler := api.NewStrictHandler(server, nil) + server := chiAPI.StrictServer{} + strictHandler := chiAPI.NewStrictHandler(server, nil) r := chi.NewRouter() - handler := api.HandlerFromMux(strictHandler, r) + handler := chiAPI.HandlerFromMux(strictHandler, r) testImpl(t, handler) } From 25e93e78dea8d25348fed682ffc985669e2cc78f Mon Sep 17 00:00:00 2001 From: Carl Bring <111354161+gnirb@users.noreply.github.com> Date: Fri, 10 Feb 2023 13:40:20 +0100 Subject: [PATCH 19/42] tweaks to get it running --- pkg/fiber-middleware/oapi_validate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go index 7457f4dc91..2d245152e3 100644 --- a/pkg/fiber-middleware/oapi_validate.go +++ b/pkg/fiber-middleware/oapi_validate.go @@ -1,6 +1,6 @@ -// Package middleware implements middleware function for go-chi or net/http, +// Package middleware implements middleware function for server implementations, // which validates incoming HTTP requests to make sure that they conform to the given OAPI 3.0 specification. -// When OAPI validation failes on the request, we return an HTTP/400. +// When OAPI validation fails on the request, we return an HTTP/400. package middleware import ( From daaeb2cfacc9d211912368d183446f54e12fd26f Mon Sep 17 00:00:00 2001 From: Carl Bring <111354161+gnirb@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:03:59 +0100 Subject: [PATCH 20/42] fixed linter errors, even though other servers seem to just ignore these --- pkg/fiber-middleware/oapi_validate.go | 18 ++++++++++-------- pkg/fiber-middleware/oapi_validate_test.go | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go index 2d245152e3..3d1352c47c 100644 --- a/pkg/fiber-middleware/oapi_validate.go +++ b/pkg/fiber-middleware/oapi_validate.go @@ -19,10 +19,8 @@ import ( "github.com/valyala/fasthttp/fasthttpadaptor" ) -const ( - ctxKeyFiberContext = "oapi-codegen/fiber-context" - ctxKeyUserData = "oapi-codegen/user-data" -) +type ctxKeyFiberContext struct{} +type ctxKeyUserData struct{} // OapiValidatorFromYamlFile creates a validator middleware from a YAML file path func OapiValidatorFromYamlFile(path string) (fiber.Handler, error) { @@ -123,14 +121,14 @@ func ValidateRequestFromContext(c *fiber.Ctx, router routers.Router, options *Op Route: route, } - // Pass the gin context into the request validator, so that any callbacks + // Pass the fiber context into the request validator, so that any callbacks // which it invokes make it available. - requestContext := context.WithValue(context.Background(), ctxKeyFiberContext, c) + requestContext := context.WithValue(context.Background(), ctxKeyFiberContext{}, c) //nolint:staticcheck if options != nil { requestValidationInput.Options = &options.Options requestValidationInput.ParamDecoder = options.ParamDecoder - requestContext = context.WithValue(requestContext, ctxKeyUserData, options.UserData) + requestContext = context.WithValue(requestContext, ctxKeyUserData{}, options.UserData) //nolint:staticcheck } err = openapi3filter.ValidateRequest(requestContext, requestValidationInput) @@ -162,7 +160,7 @@ func ValidateRequestFromContext(c *fiber.Ctx, router routers.Router, options *Op // GetFiberContext gets the fiber context from within requests. It returns // nil if not found or wrong type. func GetFiberContext(c context.Context) *fiber.Ctx { - iface := c.Value(ctxKeyFiberContext) + iface := c.Value(ctxKeyFiberContext{}) if iface == nil { return nil } @@ -174,6 +172,10 @@ func GetFiberContext(c context.Context) *fiber.Ctx { return nil } +func GetUserData(c context.Context) interface{} { + return c.Value(ctxKeyUserData{}) +} + // getMultiErrorHandlerFromOptions attempts to get the MultiErrorHandler from the options. If it is not set, // return a default handler func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler { diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 86354d8832..dd63d3eb00 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -66,7 +66,7 @@ func TestOapiRequestValidator(t *testing.T) { // access to "someScope", but disallow others. options := Options{ ErrorHandler: func(c *fiber.Ctx, message string, statusCode int) { - c.Status(statusCode).SendString("test: " + message) + _ = c.Status(statusCode).SendString("test: " + message) }, Options: openapi3filter.Options{ AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { @@ -74,7 +74,7 @@ func TestOapiRequestValidator(t *testing.T) { gCtx := GetFiberContext(c) assert.NotNil(t, gCtx) // As should user data - assert.EqualValues(t, "hi!", c.Value(ctxKeyUserData)) + assert.EqualValues(t, "hi!", GetUserData(c)) for _, s := range input.Scopes { if s == "someScope" { From 8f3997cbb05c6c7017c8a504a043bf5831c6a9e7 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 16 Feb 2023 21:27:50 +0000 Subject: [PATCH 21/42] Fix option yaml --- pkg/codegen/configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 7d5c1af88f..9b64bfe25f 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -23,7 +23,7 @@ type Configuration struct { // GenerateOptions specifies which supported output formats to generate. type GenerateOptions struct { ChiServer bool `yaml:"chi-server,omitempty"` // ChiServer specifies whether to generate chi server boilerplate - FiberServer bool `yaml:"fiber,omitempty"` // FiberServer specifies whether to generate fiber server boilerplate + FiberServer bool `yaml:"fiber-server,omitempty"` // FiberServer specifies whether to generate fiber server boilerplate EchoServer bool `yaml:"echo-server,omitempty"` // EchoServer specifies whether to generate echo server boilerplate GinServer bool `yaml:"gin-server,omitempty"` // GinServer specifies whether to generate gin server boilerplate GorillaServer bool `yaml:"gorilla-server,omitempty"` // GorillaServer specifies whether to generate Gorilla server boilerplate From e60601b132b9080b86be689b61212fd01363a03b Mon Sep 17 00:00:00 2001 From: Jleagle Date: Fri, 17 Feb 2023 08:39:51 +0000 Subject: [PATCH 22/42] Fix tests --- examples/petstore-expanded/fiber/api/server.cfg.yaml | 2 +- internal/test/strict-server/fiber/server.cfg.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/petstore-expanded/fiber/api/server.cfg.yaml b/examples/petstore-expanded/fiber/api/server.cfg.yaml index dca16f7923..91ef1331d2 100644 --- a/examples/petstore-expanded/fiber/api/server.cfg.yaml +++ b/examples/petstore-expanded/fiber/api/server.cfg.yaml @@ -1,5 +1,5 @@ package: api generate: - fiber: true + fiber-server: true embedded-spec: true output: petstore-server.gen.go diff --git a/internal/test/strict-server/fiber/server.cfg.yaml b/internal/test/strict-server/fiber/server.cfg.yaml index dd84594d48..3a6471261f 100644 --- a/internal/test/strict-server/fiber/server.cfg.yaml +++ b/internal/test/strict-server/fiber/server.cfg.yaml @@ -1,6 +1,6 @@ package: api generate: - fiber: true + fiber-server: true strict-server: true embedded-spec: true output: server.gen.go From fde3a7b98a8a0688e4d1cb17e772c237bb41a727 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Tue, 21 Feb 2023 21:34:34 +0000 Subject: [PATCH 23/42] Mod tidy --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 240683feca..08bb05a4ab 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/philhofer/fwd v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.0 // indirect From 83b391d87d194a1f1f4b61b6ad48b43ff7868f4b Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sat, 4 Mar 2023 20:17:13 +0000 Subject: [PATCH 24/42] Remove extra space --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd93bc926c..0b90e78a1b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ you can focus on implementing the business logic for your service. We have chosen to focus on [Echo](https://github.com/labstack/echo) as our default HTTP routing engine, due to its speed and simplicity for the generated stubs. [Chi](https://github.com/go-chi/chi), [Gin](https://github.com/gin-gonic/gin) -and [Fiber](https://github.com/gofiber/fiber) have also been added by contributors +and [Fiber](https://github.com/gofiber/fiber) have also been added by contributors as additional routers. We chose Echo because the `Context` object is a mockable interface, and it allows for some advanced testing. From 9b7f95e4ca88e287b4444d0b4b6d96130e6ae275 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sat, 4 Mar 2023 21:11:42 +0000 Subject: [PATCH 25/42] Check errors --- pkg/fiber-middleware/oapi_validate_test.go | 59 +++++++++++++--------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index dd63d3eb00..2737c4e13f 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -23,7 +23,7 @@ import ( //go:embed test_spec.yaml var testSchema []byte -func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { +func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { u, err := url.Parse(rawURL) if err != nil { t.Fatalf("Invalid url: %s", rawURL) @@ -33,10 +33,15 @@ func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) req.Header.Add("Accept", "application/json") req.Host = u.Host - return app.Test(req) + r, err := app.Test(req) + if err != nil { + t.Fatalf("Failed to test request: %s", rawURL) + } + + return r } -func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) (*http.Response, error) { +func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) *http.Response { u, err := url.Parse(rawURL) if err != nil { t.Fatalf("Invalid url: %s", rawURL) @@ -44,14 +49,20 @@ func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) ( buf, err := json.Marshal(jsonBody) if err != nil { - return nil, err + t.Fatalf("Failed to marshal: %s", rawURL) } + req := httptest.NewRequest("POST", u.RequestURI(), bytes.NewReader(buf)) req.Header.Add("Accept", "application/json") req.Header.Add("Content-Type", "application/json") req.Host = u.Host - return app.Test(req) + r, err := app.Test(req) + if err != nil { + t.Fatalf("Failed to test request: %s", rawURL) + } + + return r } func TestOapiRequestValidator(t *testing.T) { @@ -103,14 +114,14 @@ func TestOapiRequestValidator(t *testing.T) { }) // Let's send the request to the wrong server, this should fail validation { - res, _ := doGet(t, app, "https://not.deepmap.ai/resource") + res := doGet(t, app, "https://not.deepmap.ai/resource") assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") } // Let's send a good request, it should pass { - res, _ := doGet(t, app, "https://deepmap.ai/resource") + res := doGet(t, app, "https://deepmap.ai/resource") assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false @@ -118,7 +129,7 @@ func TestOapiRequestValidator(t *testing.T) { // Send an out-of-spec parameter { - res, _ := doGet(t, app, "https://deepmap.ai/resource?id=500") + res := doGet(t, app, "https://deepmap.ai/resource?id=500") assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false @@ -126,7 +137,7 @@ func TestOapiRequestValidator(t *testing.T) { // Send a bad parameter type { - res, _ := doGet(t, app, "https://deepmap.ai/resource?id=foo") + res := doGet(t, app, "https://deepmap.ai/resource?id=foo") assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false @@ -146,7 +157,7 @@ func TestOapiRequestValidator(t *testing.T) { }{ Name: "Marcin", } - res, _ := doPost(t, app, "https://deepmap.ai/resource", body) + res := doPost(t, app, "https://deepmap.ai/resource", body) assert.Equal(t, http.StatusNoContent, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false @@ -159,7 +170,7 @@ func TestOapiRequestValidator(t *testing.T) { }{ Name: 7, } - res, _ := doPost(t, app, "https://deepmap.ai/resource", body) + res := doPost(t, app, "https://deepmap.ai/resource", body) assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false @@ -172,7 +183,7 @@ func TestOapiRequestValidator(t *testing.T) { // Call a protected function to which we have access { - res, _ := doGet(t, app, "https://deepmap.ai/protected_resource") + res := doGet(t, app, "https://deepmap.ai/protected_resource") assert.Equal(t, http.StatusNoContent, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false @@ -184,7 +195,7 @@ func TestOapiRequestValidator(t *testing.T) { }) // Call a protected function to which we don't have access { - res, _ := doGet(t, app, "https://deepmap.ai/protected_resource2") + res := doGet(t, app, "https://deepmap.ai/protected_resource2") assert.Equal(t, http.StatusBadRequest, res.StatusCode) assert.False(t, called, "Handler should not have been called") called = false @@ -196,7 +207,7 @@ func TestOapiRequestValidator(t *testing.T) { }) // Call a protected function without credentials { - res, _ := doGet(t, app, "https://deepmap.ai/protected_resource_401") + res := doGet(t, app, "https://deepmap.ai/protected_resource_401") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -238,7 +249,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { // Let's send a good request, it should pass { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false @@ -247,7 +258,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { // Let's send a request with a missing parameter, it should return // a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -262,7 +273,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { // Let's send a request with a 2 missing parameters, it should return // a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource") + res := doGet(t, app, "https://deepmap.ai/multiparamresource") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -279,7 +290,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { // Let's send a request with a 1 missing parameter, and another outside // or the parameters. It should return a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -296,7 +307,7 @@ func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { // Let's send a request with a parameters that do not meet spec. It should // return a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -345,7 +356,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a good request, it should pass { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50&id2=50") assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, called, "Handler should have been called") called = false @@ -354,7 +365,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a missing parameter, it should return // a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=50") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -369,7 +380,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a 2 missing parameters, it should return // a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource") + res := doGet(t, app, "https://deepmap.ai/multiparamresource") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -386,7 +397,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a 1 missing parameter, and another outside // or the parameters. It should return a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=500") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { @@ -403,7 +414,7 @@ func TestOapiRequestValidatorWithOptionsMultiErrorAndCustomHandler(t *testing.T) // Let's send a request with a parameters that do not meet spec. It should // return a bad status { - res, _ := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") + res := doGet(t, app, "https://deepmap.ai/multiparamresource?id=abc&id2=1") assert.Equal(t, http.StatusBadRequest, res.StatusCode) body, err := io.ReadAll(res.Body) if assert.NoError(t, err) { From 0579a04b9d2cca2aa00c8c722358c930e7a8e65c Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sat, 4 Mar 2023 21:30:41 +0000 Subject: [PATCH 26/42] Update pkg/fiber-middleware/oapi_validate_test.go Co-authored-by: leonklingele --- pkg/fiber-middleware/oapi_validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 2737c4e13f..4d81fac437 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -35,7 +35,7 @@ func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { r, err := app.Test(req) if err != nil { - t.Fatalf("Failed to test request: %s", rawURL) + t.Fatalf("Failed to test request, URL=%q: %v", rawURL, err) } return r From 9950d5f29ec3cf15ee1c1847d68bcec0b5a7e332 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 5 Mar 2023 17:26:05 +0000 Subject: [PATCH 27/42] Update pkg/fiber-middleware/oapi_validate_test.go Co-authored-by: leonklingele --- pkg/fiber-middleware/oapi_validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 4d81fac437..326d1dd642 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -59,7 +59,7 @@ func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) * r, err := app.Test(req) if err != nil { - t.Fatalf("Failed to test request: %s", rawURL) + t.Fatalf("Failed to test request, URL=%q: %v", rawURL, err) } return r From 72f97a0ad952b802a0652ec465a8b4d9a8513c44 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 5 Mar 2023 17:31:17 +0000 Subject: [PATCH 28/42] Update pkg/fiber-middleware/oapi_validate_test.go Co-authored-by: leonklingele --- pkg/fiber-middleware/oapi_validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 326d1dd642..bab15a8cb8 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -49,7 +49,7 @@ func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) * buf, err := json.Marshal(jsonBody) if err != nil { - t.Fatalf("Failed to marshal: %s", rawURL) + t.Fatalf("failed to marshal: %v", err) } req := httptest.NewRequest("POST", u.RequestURI(), bytes.NewReader(buf)) From f7374b7b549a39aa178e4f850b186a45db1e82ca Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 5 Mar 2023 17:40:49 +0000 Subject: [PATCH 29/42] Update pkg/fiber-middleware/oapi_validate_test.go Co-authored-by: leonklingele --- pkg/fiber-middleware/oapi_validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index bab15a8cb8..62ab5200e6 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -26,7 +26,7 @@ var testSchema []byte func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { u, err := url.Parse(rawURL) if err != nil { - t.Fatalf("Invalid url: %s", rawURL) + t.Fatalf("invalid URL %q: %v", rawURL, err) } req := httptest.NewRequest("GET", u.RequestURI(), nil) From 532ee1d2d63552217565504f2da365fc7bf0d305 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 5 Mar 2023 17:40:55 +0000 Subject: [PATCH 30/42] Update pkg/fiber-middleware/oapi_validate_test.go Co-authored-by: leonklingele --- pkg/fiber-middleware/oapi_validate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index 62ab5200e6..c099605a48 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -44,7 +44,7 @@ func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) *http.Response { u, err := url.Parse(rawURL) if err != nil { - t.Fatalf("Invalid url: %s", rawURL) + t.Fatalf("invalid url %q: %v", rawURL, err) } buf, err := json.Marshal(jsonBody) From 75cf7477aaed1db03103cae38500832afcf885e8 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Mon, 10 Apr 2023 21:25:51 +0100 Subject: [PATCH 31/42] Separate oapi-codegen imports --- examples/petstore-expanded/fiber/petstore.go | 3 ++- examples/petstore-expanded/fiber/petstore_test.go | 3 ++- internal/test/strict-server/strict_test.go | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go index 0277f93c08..344752bbbc 100644 --- a/examples/petstore-expanded/fiber/petstore.go +++ b/examples/petstore-expanded/fiber/petstore.go @@ -10,9 +10,10 @@ import ( "log" "os" + "github.com/gofiber/fiber/v2" + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" middleware "github.com/deepmap/oapi-codegen/pkg/fiber-middleware" - "github.com/gofiber/fiber/v2" ) func main() { diff --git a/examples/petstore-expanded/fiber/petstore_test.go b/examples/petstore-expanded/fiber/petstore_test.go index ed900c3dcc..e6a103ace9 100644 --- a/examples/petstore-expanded/fiber/petstore_test.go +++ b/examples/petstore-expanded/fiber/petstore_test.go @@ -9,9 +9,10 @@ import ( "net/url" "testing" - "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" ) func doGet(t *testing.T, app *fiber.App, rawURL string) (*http.Response, error) { diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index 6c3d72a857..87b44668fc 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -11,7 +11,6 @@ import ( "strings" "testing" - "github.com/gin-gonic/gin" "github.com/go-chi/chi/v5" "github.com/gofiber/adaptor/v2" From 84953e7d974c1b8a728ab89cd5710c767207b3eb Mon Sep 17 00:00:00 2001 From: Jleagle Date: Tue, 11 Apr 2023 09:01:04 +0100 Subject: [PATCH 32/42] Use t.Helper() --- pkg/fiber-middleware/oapi_validate_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go index c099605a48..9201e13fb8 100644 --- a/pkg/fiber-middleware/oapi_validate_test.go +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -24,6 +24,8 @@ import ( var testSchema []byte func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { + t.Helper() + u, err := url.Parse(rawURL) if err != nil { t.Fatalf("invalid URL %q: %v", rawURL, err) @@ -42,6 +44,8 @@ func doGet(t *testing.T, app *fiber.App, rawURL string) *http.Response { } func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) *http.Response { + t.Helper() + u, err := url.Parse(rawURL) if err != nil { t.Fatalf("invalid url %q: %v", rawURL, err) From fab7a4843243acd9d4bbf4ec69e36a377f75d260 Mon Sep 17 00:00:00 2001 From: Carl Bring <111354161+gnirb@users.noreply.github.com> Date: Tue, 11 Apr 2023 11:27:38 +0200 Subject: [PATCH 33/42] fixing comments --- .../fiber/api/petstore-server.gen.go | 3 +- .../test/strict-server/fiber/server.gen.go | 2 +- internal/test/strict-server/fiber/server.go | 41 ++++--------------- .../templates/fiber/fiber-middleware.tmpl | 3 +- .../strict/strict-fiber-interface.tmpl | 9 ++-- 5 files changed, 17 insertions(+), 41 deletions(-) diff --git a/examples/petstore-expanded/fiber/api/petstore-server.gen.go b/examples/petstore-expanded/fiber/api/petstore-server.gen.go index 3c691f35b3..a55486c1da 100644 --- a/examples/petstore-expanded/fiber/api/petstore-server.gen.go +++ b/examples/petstore-expanded/fiber/api/petstore-server.gen.go @@ -48,7 +48,8 @@ func (siw *ServerInterfaceWrapper) FindPets(c *fiber.Ctx) error { // Parameter object where we will unmarshal all parameters from the context var params FindPetsParams - query, err := url.ParseQuery(string(c.Request().URI().QueryString())) + var query url.Values + query, err = url.ParseQuery(string(c.Request().URI().QueryString())) if err != nil { return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) } diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index 97ad0c0ab9..9300b19aa0 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -361,7 +361,7 @@ type ReusableResponsesResponseObject interface { VisitReusableResponsesResponse(ctx *fiber.Ctx) error } -type ReusableResponses200JSONResponse = ReusableresponseJSONResponse +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(ctx *fiber.Ctx) error { ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) diff --git a/internal/test/strict-server/fiber/server.go b/internal/test/strict-server/fiber/server.go index fab221b40d..5b7026809e 100644 --- a/internal/test/strict-server/fiber/server.go +++ b/internal/test/strict-server/fiber/server.go @@ -22,21 +22,17 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa part, err := request.Body.NextPart() if err == io.EOF { return nil - } - if err != 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 } @@ -47,10 +43,7 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { switch { case request.Body != nil: - return MultipleRequestAndResponseTypes200ImagepngResponse{ - Body: request.Body, - ContentLength: 0, - }, nil + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil case request.JSONBody != nil: return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil case request.FormdataBody != nil: @@ -63,21 +56,17 @@ func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, reque part, err := request.MultipartBody.NextPart() if err == io.EOF { return nil - } - if err != 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 } @@ -93,18 +82,11 @@ func (s StrictServer) TextExample(ctx context.Context, request TextExampleReques } func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) { - return UnknownExample200Videomp4Response{ - Body: request.Body, - ContentLength: 0, - }, nil + return UnknownExample200Videomp4Response{Body: request.Body}, nil } func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { - return UnspecifiedContentType200VideoResponse{ - Body: request.Body, - ContentType: request.ContentType, - ContentLength: 0, - }, nil + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, nil } func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedExampleRequestObject) (URLEncodedExampleResponseObject, error) { @@ -112,18 +94,9 @@ func (s StrictServer) URLEncodedExample(ctx context.Context, request URLEncodedE } func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExampleRequestObject) (HeadersExampleResponseObject, error) { - return HeadersExample200JSONResponse{ - Body: *request.Body, - Headers: HeadersExample200ResponseHeaders{ - Header1: request.Params.Header1, - Header2: *request.Params.Header2, - }, - }, nil + return HeadersExample200JSONResponse{Body: *request.Body, Headers: HeadersExample200ResponseHeaders{Header1: request.Params.Header1, Header2: *request.Params.Header2}}, nil } func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { - return ReusableResponses200JSONResponse{ - Body: *request.Body, - Headers: ReusableresponseResponseHeaders{}, - }, nil + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } diff --git a/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/pkg/codegen/templates/fiber/fiber-middleware.tmpl index 4ea9f21357..fd8d00dc15 100644 --- a/pkg/codegen/templates/fiber/fiber-middleware.tmpl +++ b/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -44,7 +44,8 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { var params {{.OperationId}}Params {{if .QueryParams}} - query, err := url.ParseQuery(string(c.Request().URI().QueryString())) + var query url.Values + query, err = url.ParseQuery(string(c.Request().URI().QueryString())) if err != nil { return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error()) } diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl index 683f842ea2..ed39d1c8d7 100644 --- a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -37,12 +37,13 @@ {{end}} {{range .Contents}} + {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} {{if and $fixedStatusCode $isRef -}} - type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response = {{$ref}}{{.NameTagOrContentType}}Response + type {{$receiverTypeName}} struct{ {{$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}} + type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{else -}} - type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response struct { + type {{$receiverTypeName}} 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 @@ -62,7 +63,7 @@ } {{end}} - func (response {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) Visit{{$opid}}Response(ctx *fiber.Ctx) error { + func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx *fiber.Ctx) error { {{range $headers -}} ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) {{end -}} From 4030fffcb6f553ba206234abc9723a4269ac3acc Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 17:50:46 +0100 Subject: [PATCH 34/42] Sort imports --- pkg/codegen/templates/imports.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl index 3da81b082b..f69a492038 100644 --- a/pkg/codegen/templates/imports.tmpl +++ b/pkg/codegen/templates/imports.tmpl @@ -27,8 +27,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/labstack/echo/v4" "github.com/gin-gonic/gin" - "github.com/gorilla/mux" "github.com/gofiber/fiber/v2" + "github.com/gorilla/mux" {{- range .ExternalImports}} {{ . }} {{- end}} From 25b5227674ceb488cab5cc321ce1f0401c768f74 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 20:43:39 +0100 Subject: [PATCH 35/42] Use WriteString --- internal/test/strict-server/fiber/server.gen.go | 4 ++-- pkg/codegen/templates/strict/strict-fiber-interface.tmpl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index 9300b19aa0..ae06766f24 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -301,7 +301,7 @@ func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultiple if form, err := runtime.MarshalForm(response, nil); err != nil { return err } else { - _, err := ctx.Write([]byte(form.Encode())) + _, err := ctx.WriteString(form.Encode()) return err } } @@ -543,7 +543,7 @@ func (response URLEncodedExample200FormdataResponse) VisitURLEncodedExampleRespo if form, err := runtime.MarshalForm(response, nil); err != nil { return err } else { - _, err := ctx.Write([]byte(form.Encode())) + _, err := ctx.WriteString(form.Encode()) return err } } diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl index ed39d1c8d7..6fdef0981f 100644 --- a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -87,7 +87,7 @@ if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil { return err } else { - _, err := ctx.Write([]byte(form.Encode())) + _, err := ctx.WriteString(form.Encode()) return err } {{else if eq .NameTag "Multipart" -}} From 6198c7fffa0a2627a22630e7c46ee32a0f961872 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 21:44:37 +0100 Subject: [PATCH 36/42] Fix imports --- internal/test/strict-server/strict_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index 87b44668fc..d0ad4231f9 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -18,8 +18,7 @@ import ( "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" - chiAPI "github.com/gin-gonic/gin" + chiAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/chi" clientAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/client" echoAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/echo" fiberAPI "github.com/deepmap/oapi-codegen/internal/test/strict-server/fiber" From 5d342cb5b91f3feab3ac88b8376676c4f016ec16 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 21:44:43 +0100 Subject: [PATCH 37/42] Mods --- go.mod | 19 +++++++++++--- go.sum | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 08bb05a4ab..9f71ea0f0d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( github.com/getkin/kin-openapi v0.116.0 github.com/gin-gonic/gin v1.9.0 github.com/go-chi/chi/v5 v5.0.8 + github.com/gofiber/adaptor/v2 v2.2.1 + github.com/gofiber/fiber/v2 v2.45.0 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 @@ -12,12 +14,14 @@ require ( github.com/lestrrat-go/jwx v1.2.25 github.com/matryer/moq v0.3.1 github.com/stretchr/testify v1.8.2 + github.com/valyala/fasthttp v1.47.0 golang.org/x/text v0.9.0 golang.org/x/tools v0.8.0 gopkg.in/yaml.v2 v2.4.0 ) require ( + github.com/andybalholm/brotli v1.0.5 // indirect github.com/bytedance/sonic v1.8.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -33,6 +37,7 @@ require ( github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.5 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -43,25 +48,31 @@ require ( github.com/lestrrat-go/option v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect - github.com/philhofer/fwd v1.1.1 // indirect + github.com/philhofer/fwd v1.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/stretchr/objx v0.5.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.9 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index f5edf9d5cc..1a30a32f12 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -41,6 +43,10 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= +github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= +github.com/gofiber/fiber/v2 v2.45.0 h1:p4RpkJT9GAW6parBSbcNFH2ApnAuW3OzaQzbOCoDu+s= +github.com/gofiber/fiber/v2 v2.45.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -60,6 +66,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -98,8 +106,10 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -112,11 +122,22 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -131,6 +152,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= @@ -139,39 +163,82 @@ github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From dfafdc4a85f0d436c6ce9096af8070e731a3f115 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 21:51:31 +0100 Subject: [PATCH 38/42] Generate --- .../test/strict-server/fiber/server.gen.go | 101 +++++++++++++++--- 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index ae06766f24..17cb4f1446 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -33,6 +33,9 @@ type ServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(c *fiber.Ctx) error + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(c *fiber.Ctx, pType string) error + // (POST /reusable-responses) ReusableResponses(c *fiber.Ctx) error @@ -77,6 +80,22 @@ func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(c *fiber.Ctx) return siw.Handler.MultipleRequestAndResponseTypes(c) } +// ReservedGoKeywordParameters operation middleware +func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameter("simple", false, "type", c.Params("type"), &pType) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter type: %w", err).Error()) + } + + return siw.Handler.ReservedGoKeywordParameters(c, pType) +} + // ReusableResponses operation middleware func (siw *ServerInterfaceWrapper) ReusableResponses(c *fiber.Ctx) error { @@ -176,6 +195,8 @@ func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, option router.Post(options.BaseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.Get(options.BaseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) + router.Post(options.BaseURL+"/reusable-responses", wrapper.ReusableResponses) router.Post(options.BaseURL+"/text", wrapper.TextExample) @@ -353,6 +374,24 @@ func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestA return nil } +type ReservedGoKeywordParametersRequestObject struct { + Type string `json:"type"` +} + +type ReservedGoKeywordParametersResponseObject interface { + VisitReservedGoKeywordParametersResponse(ctx *fiber.Ctx) error +} + +type ReservedGoKeywordParameters200TextResponse string + +func (response ReservedGoKeywordParameters200TextResponse) VisitReservedGoKeywordParametersResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "text/plain") + ctx.Status(200) + + _, err := ctx.WriteString(string(response)) + return err +} + type ReusableResponsesRequestObject struct { Body *ReusableResponsesJSONRequestBody } @@ -620,6 +659,9 @@ type StrictServerInterface interface { // (POST /multiple) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) + // (GET /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) + // (POST /reusable-responses) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) @@ -761,6 +803,33 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error { return nil } +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx *fiber.Ctx, pType string) error { + var request ReservedGoKeywordParametersRequestObject + + request.PType = pType + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx.UserContext(), request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + if err := validResponse.VisitReservedGoKeywordParametersResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // ReusableResponses operation middleware func (sh *strictHandler) ReusableResponses(ctx *fiber.Ctx) error { var request ReusableResponsesRequestObject @@ -944,21 +1013,23 @@ func (sh *strictHandler) HeadersExample(ctx *fiber.Ctx, params HeadersExamplePar // 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", + "H4sIAAAAAAAC/+xYX2/bNhD/KgS3p0KynDRPeluDotu6rYOTPg15oMWzzFYiuePJimHouw8UJf+pFS/O", + "7BgY+mbRd787/u4Pj1zxzJTWaNDkeLriCM4a7aD9mAqJ8HcFjvyXBJehsqSM5il/J+Sk+6+JOELlxLSA", + "Xt3LZ0YT6FZVWFuoTHjV5Ivz+ivusjmUwv/6EWHGU/5DsnElCf+6BB5FaQvgTdNE33jw6SOP+ByEBGy9", + "DT+vdrFpaYGn3BEqnXMPEsSuB8WUJsgBvTUv2jnhBXo/0hW3aCwgqcDRQhQVDFvqVsz0C2QUdqD0zOxz", + "eWs0CaUdk2o2AwRNrCOPeQzHXGWtQQLJpkvmLWTEHOACkEecFHnH+N32OuscdjziC0AXDF2NxqOxj5ex", + "oIVVPOVv26WIW0HzdkPrAFkzFPdf7z79wZRjoiJTClKZKIolKwW6uSgKkExpMt7HKiM34q0pbCP/i+zU", + "33dc+rRpM+idkctzZEybmFv5fD0ev1JiNhG/CcaGMNZOJVsV1sLMRFUMkP5Zf9Wm1gwQDXY7S8qqIGUF", + "0nawdtn+vRd5DuVrvGRmsIylIHEm1k9l6aLEd81gsEju5qZ2bG5qRoZJEAWrFc1Zr/hNdSvNBHNK5wWw", + "3qloMJIFdD33Jy0n3V7uPcbZaynaQXmM67qO2+BVWIDOjAT5MlhVihwSq/NddY8tiKd8uiSftvvd9URJ", + "FHGCR0psIZQ+fHS8Ujv5zvTJCjuUK0J7JMo4N/FXWNYGZWwFihII0CUrb73xwDkMlPKfa0mWCc2mwLQo", + "QTIxI0D2wbAO0u2V7KSz+8F8DCIbqPa8XX+kf624p6Q9g3nEvQGeBlZCXSv0QSesIDpA28O/5ud/CkDP", + "Zpj04h1Tw22wb1Fr6hBmzrfEocgN8BcsTbYkLjMwHM64vdn3Nc4gH8mnz/17eHzWkX/C1vfatX0sYVVY", + "fJqzTus5tL2wkz6DxYWSYJLS3hyJfDFSnYVMzRTIuNtFHHx7qiXcGp0h0O4I5O8T2hBbg/lrDs2BBQYi", + "5gyrgZWVI2aFc0xR20UKFa5KEvaax+eNZ7fB0v2mnR6K6pszxfTNpSJ6M746XuXtmfNmZ5R5oh4nv70P", + "MsfeF082Mx058Z3O7oXK2V9S4q0XleES/jkIbM70DNTCT0RaMgSqUINkCyX6R4C92uwANmEdmoWCG5tp", + "qH/dOWYgig5iXfPo0AvQw//4eeKc72bnztMm4uGJKyRLhYWPKJFNkyQ8jY1cLfIccKRMIqzizUPzTwAA", + "AP//O0NNuucUAAA=", } // GetSwagger returns the content of the embedded swagger specification file From 22089bfbfbebbc9dc35073c996cd220c3a39fc3b Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sun, 14 May 2023 21:56:35 +0100 Subject: [PATCH 39/42] Add missing config --- cmd/oapi-codegen/oapi-codegen.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index 9f4c2f4c10..ed29562657 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -445,6 +445,8 @@ func generationTargets(cfg *codegen.Configuration, targets []string) error { switch opt { case "chi-server", "chi": opts.ChiServer = true + case "fiber-server", "fiber": + opts.FiberServer = true case "server", "echo-server", "echo": opts.EchoServer = true case "gin", "gin-server": From c58882a5114d4f3a7783fa06c2d3976b1c5979a5 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sat, 27 May 2023 10:47:50 +0100 Subject: [PATCH 40/42] Use new adaptor --- go.mod | 5 ++--- go.sum | 8 ++++---- internal/test/strict-server/strict_test.go | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 9f71ea0f0d..377927260c 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,7 @@ require ( github.com/getkin/kin-openapi v0.116.0 github.com/gin-gonic/gin v1.9.0 github.com/go-chi/chi/v5 v5.0.8 - github.com/gofiber/adaptor/v2 v2.2.1 - github.com/gofiber/fiber/v2 v2.45.0 + github.com/gofiber/fiber/v2 v2.46.0 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 @@ -48,7 +47,7 @@ require ( github.com/lestrrat-go/option v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go.sum b/go.sum index 1a30a32f12..f91ee67d36 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= -github.com/gofiber/fiber/v2 v2.45.0 h1:p4RpkJT9GAW6parBSbcNFH2ApnAuW3OzaQzbOCoDu+s= -github.com/gofiber/fiber/v2 v2.45.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= +github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= +github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -106,8 +106,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/internal/test/strict-server/strict_test.go b/internal/test/strict-server/strict_test.go index d0ad4231f9..bfbb9bc2ab 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -13,8 +13,8 @@ import ( "github.com/gin-gonic/gin" "github.com/go-chi/chi/v5" - "github.com/gofiber/adaptor/v2" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" From c6e3a43d930b9e7b5af9d1964bffaa829e9a4a76 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Sat, 27 May 2023 10:52:30 +0100 Subject: [PATCH 41/42] Use new adaptor --- go.mod | 2 +- go.sum | 2 -- pkg/fiber-middleware/oapi_validate.go | 6 ++---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 377927260c..1385f7fbe8 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/lestrrat-go/jwx v1.2.25 github.com/matryer/moq v0.3.1 github.com/stretchr/testify v1.8.2 - github.com/valyala/fasthttp v1.47.0 golang.org/x/text v0.9.0 golang.org/x/tools v0.8.0 gopkg.in/yaml.v2 v2.4.0 @@ -65,6 +64,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.9 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.47.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect diff --git a/go.sum b/go.sum index f91ee67d36..dea1a1bdca 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,6 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= -github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= diff --git a/pkg/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go index 3d1352c47c..ef30dd8f75 100644 --- a/pkg/fiber-middleware/oapi_validate.go +++ b/pkg/fiber-middleware/oapi_validate.go @@ -16,7 +16,7 @@ import ( "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/gorillamux" "github.com/gofiber/fiber/v2" - "github.com/valyala/fasthttp/fasthttpadaptor" + "github.com/gofiber/fiber/v2/middleware/adaptor" ) type ctxKeyFiberContext struct{} @@ -91,9 +91,7 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) fibe // of validating a request. func ValidateRequestFromContext(c *fiber.Ctx, router routers.Router, options *Options) error { - r := &http.Request{} - - err := fasthttpadaptor.ConvertRequest(c.Context(), r, false) + r, err := adaptor.ConvertRequest(c, false) if err != nil { return err } From 7fc79c6d9be0809ae3671d8ca2b2fab940d1af7a Mon Sep 17 00:00:00 2001 From: marcinromaszewicz Date: Fri, 2 Jun 2023 09:28:07 -0700 Subject: [PATCH 42/42] Fix compilation and test issues - Update modules - Add missing test handler - Fix variable naming in strict server wrappers --- go.mod | 11 ++++ go.sum | 63 +++++++++++++++++++ .../test/strict-server/fiber/server.gen.go | 2 +- internal/test/strict-server/fiber/server.go | 4 ++ .../templates/strict/strict-fiber.tmpl | 2 +- 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 705e680304..aeff31dfb4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/getkin/kin-openapi v0.117.0 github.com/gin-gonic/gin v1.9.1 github.com/go-chi/chi/v5 v5.0.8 + github.com/gofiber/fiber/v2 v2.46.0 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 @@ -18,6 +19,7 @@ require ( ) require ( + github.com/andybalholm/brotli v1.0.5 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -34,6 +36,7 @@ require ( github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.3 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -45,18 +48,26 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/philhofer/fwd v1.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/stretchr/objx v0.5.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.47.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.10.0 // indirect diff --git a/go.sum b/go.sum index 080bf51bb3..f11b87036d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -43,6 +45,8 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= +github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -62,6 +66,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -102,6 +108,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -115,10 +123,20 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -134,6 +152,9 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= @@ -142,42 +163,84 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.9.2 h1:UXbndbirwCAx6TULftIfie/ygDNCwxEie+IiNP1IcNc= golang.org/x/tools v0.9.2/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index 17cb4f1446..84671503d0 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -807,7 +807,7 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error { func (sh *strictHandler) ReservedGoKeywordParameters(ctx *fiber.Ctx, pType string) error { var request ReservedGoKeywordParametersRequestObject - request.PType = pType + request.Type = pType handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { return sh.ssi.ReservedGoKeywordParameters(ctx.UserContext(), request.(ReservedGoKeywordParametersRequestObject)) diff --git a/internal/test/strict-server/fiber/server.go b/internal/test/strict-server/fiber/server.go index 5b7026809e..b0724db19f 100644 --- a/internal/test/strict-server/fiber/server.go +++ b/internal/test/strict-server/fiber/server.go @@ -100,3 +100,7 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), nil +} diff --git a/pkg/codegen/templates/strict/strict-fiber.tmpl b/pkg/codegen/templates/strict/strict-fiber.tmpl index 7ec8293171..38a819f71b 100644 --- a/pkg/codegen/templates/strict/strict-fiber.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber.tmpl @@ -19,7 +19,7 @@ type strictHandler struct { {{range .PathParams -}} {{$varName := .GoVariableName -}} - request.{{$varName | ucFirst}} = {{$varName}} + request.{{.GoName}} = {{.GoVariableName}} {{end -}} {{if .RequiresParamObject -}}