diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index 7d9f2cc57a..40fdba92d5 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -90,7 +90,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", "fiber", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune"`) 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") @@ -443,6 +443,8 @@ func generationTargets(cfg *codegen.Configuration, targets []string) error { opts.ChiServer = true case "server", "echo-server", "echo": opts.EchoServer = true + case "fiber", "fiber-server": + opts.FiberServer = true case "gin", "gin-server": opts.GinServer = true case "gorilla", "gorilla-server": diff --git a/examples/petstore-expanded/fiber/api/models.cfg.yaml b/examples/petstore-expanded/fiber/api/models.cfg.yaml new file mode 100644 index 0000000000..c1b80a89d9 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/models.cfg.yaml @@ -0,0 +1,4 @@ +package: models +generate: + models: true +output: models/models.gen.go diff --git a/examples/petstore-expanded/fiber/api/models/models.gen.go b/examples/petstore-expanded/fiber/api/models/models.gen.go new file mode 100644 index 0000000000..32ad304419 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/models/models.gen.go @@ -0,0 +1,46 @@ +// Package models provides primitives to interact with the openapi HTTP API. +// +// Code generated by unknown module path version unknown version DO NOT EDIT. +package models + +// 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 diff --git a/examples/petstore-expanded/fiber/api/petstore-server.gen.go b/examples/petstore-expanded/fiber/api/petstore-server.gen.go new file mode 100644 index 0000000000..7f587c03e0 --- /dev/null +++ b/examples/petstore-expanded/fiber/api/petstore-server.gen.go @@ -0,0 +1,269 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by unknown module path version unknown version DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + . "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api/models" + "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 { + // Returns all pets + // (Get /pets) + FindPets(ctx *fiber.Ctx, params FindPetsParams) error + // Creates a new pet + // (Post /pets) + AddPet(ctx *fiber.Ctx) error + // Deletes a pet by ID + // (Delete /pets/{id}) + DeletePet(ctx *fiber.Ctx, id int64) error + // Returns a pet by ID + // (Get /pets/{id}) + FindPetByID(ctx *fiber.Ctx, id int64) error +} + +// ServerInterfaceWrapper converts fiber contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// FindPets converts fiber context to params. +func (w *ServerInterfaceWrapper) FindPets(ctx *fiber.Ctx) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + + bURL := ctx.Request().URI().FullURI() + sURL := string(bURL) + u, err := url.Parse(sURL) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameters: %s", err)) + } + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", u.Query(), ¶ms.Tags) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tags: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", u.Query(), ¶ms.Limit) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.FindPets(ctx, params) + return err +} + +// AddPet converts fiber context to params. +func (w *ServerInterfaceWrapper) AddPet(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.AddPet(ctx) + return err +} + +// DeletePet converts fiber context to params. +func (w *ServerInterfaceWrapper) DeletePet(ctx *fiber.Ctx) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Params("id"), &id) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.DeletePet(ctx, id) + return err +} + +// FindPetByID converts fiber context to params. +func (w *ServerInterfaceWrapper) FindPetByID(ctx *fiber.Ctx) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Params("id"), &id) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.FindPetByID(ctx, id) + return err +} + +// This is a simple interface which specifies fiber.Router addition functions which +// are present on both fiber.App and fiber.Router, since we want to allow using +// either of them for path registration +type FiberRouter interface { + Use(args ...interface{}) fiber.Router + + Get(path string, handlers ...fiber.Handler) fiber.Router + Head(path string, handlers ...fiber.Handler) fiber.Router + Post(path string, handlers ...fiber.Handler) fiber.Router + Put(path string, handlers ...fiber.Handler) fiber.Router + Delete(path string, handlers ...fiber.Handler) fiber.Router + Connect(path string, handlers ...fiber.Handler) fiber.Router + Options(path string, handlers ...fiber.Handler) fiber.Router + Trace(path string, handlers ...fiber.Handler) fiber.Router + Patch(path string, handlers ...fiber.Handler) fiber.Router + + Add(method, path string, handlers ...fiber.Handler) fiber.Router + Static(prefix, root string, config ...fiber.Static) fiber.Router + All(path string, handlers ...fiber.Handler) fiber.Router + + Group(prefix string, handlers ...fiber.Handler) fiber.Router + + Route(prefix string, fn func(router fiber.Router), name ...string) fiber.Router + + Mount(prefix string, fiber *fiber.App) fiber.Router + + Name(name string) fiber.Router +} + +// RegisterHandlers adds each server route to the FiberRouter. +func RegisterHandlers(router FiberRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router FiberRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Get(baseURL+"/pets", wrapper.FindPets) + router.Post(baseURL+"/pets", wrapper.AddPet) + router.Delete(baseURL+"/pets/:id", wrapper.DeletePet) + router.Get(baseURL+"/pets/:id", wrapper.FindPetByID) + +} + +// 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(_ *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..aa3db2daad --- /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=models.cfg.yaml ../../petstore-expanded.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.cfg.yaml ../../petstore-expanded.yaml +package api + +import ( + "fmt" + "net/http" + "sync" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api/models" + "github.com/gofiber/fiber/v2" +) + +type PetStore struct { + Pets map[int64]models.Pet + NextId int64 + Lock sync.Mutex +} + +func NewPetStore() *PetStore { + return &PetStore{ + Pets: make(map[int64]models.Pet), + NextId: 1000, + } +} + +// This function wraps sending of an error in the Error format, and +// handling the failure to marshal that. +func sendPetStoreError(ctx *fiber.Ctx, code int, message string) error { + petErr := models.Error{ + Code: int32(code), + Message: message, + } + err := ctx.Status(code).JSON(petErr) + return err +} + +// FindPets implements all the handlers in the ServerInterface +func (p *PetStore) FindPets(ctx *fiber.Ctx, params models.FindPetsParams) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + var result []models.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 ctx.Status(http.StatusOK).JSON(result) +} + +func (p *PetStore) AddPet(ctx *fiber.Ctx) error { + // We expect a NewPet object in the request body. + var newPet models.NewPet + err := ctx.BodyParser(&newPet) + if err != nil { + return sendPetStoreError(ctx, 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 models.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 + err = ctx.Status(http.StatusCreated).JSON(pet) + if err != nil { + // Something really bad happened, tell Fiber that our handler failed + return err + } + + // Return no error. This refers to the handler. Even if we return an HTTP + // error, but everything else is working properly, tell Fiber that we serviced + // the error. We should only return errors from Fiber handlers if the actual + // servicing of the error on the infrastructure level failed. Returning an + // HTTP/400 or HTTP/500 from here means Fiber/HTTP are still working, so + // return nil. + return nil +} + +func (p *PetStore) FindPetByID(ctx *fiber.Ctx, petId int64) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + pet, found := p.Pets[petId] + if !found { + return sendPetStoreError(ctx, http.StatusNotFound, + fmt.Sprintf("Could not find pet with ID %d", petId)) + } + return ctx.Status(http.StatusOK).JSON(pet) +} + +func (p *PetStore) DeletePet(ctx *fiber.Ctx, id int64) error { + p.Lock.Lock() + defer p.Lock.Unlock() + + _, found := p.Pets[id] + if !found { + return sendPetStoreError(ctx, http.StatusNotFound, + fmt.Sprintf("Could not find pet with ID %d", id)) + } + delete(p.Pets, id) + return ctx.SendStatus(http.StatusNoContent) +} diff --git a/examples/petstore-expanded/fiber/api/server.cfg.yaml b/examples/petstore-expanded/fiber/api/server.cfg.yaml new file mode 100644 index 0000000000..19a7ff684a --- /dev/null +++ b/examples/petstore-expanded/fiber/api/server.cfg.yaml @@ -0,0 +1,8 @@ +package: api +output: petstore-server.gen.go +additional-imports: + - package: github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api/models + alias: . +generate: + fiber-server: true + embedded-spec: true diff --git a/examples/petstore-expanded/fiber/petstore.go b/examples/petstore-expanded/fiber/petstore.go new file mode 100644 index 0000000000..115ba321a4 --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore.go @@ -0,0 +1,49 @@ +// 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 +// +// The code under api/petstore/ has been generated from that specification. +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +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 app + app := fiber.New() + // Log all requests + app.Use(logger.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.RegisterHandlers(app, petStore) + + // 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..9c4e1a187c --- /dev/null +++ b/examples/petstore-expanded/fiber/petstore_test.go @@ -0,0 +1,158 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" + "testing" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api" + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/fiber/api/models" + middleware "github.com/deepmap/oapi-codegen/pkg/fiber-middleware" + "github.com/deepmap/oapi-codegen/pkg/testutil" + "github.com/gofiber/adaptor/v2" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPetStore(t *testing.T) { + var err error + // Here, we Initialize fiber app + app := fiber.New() + + // Now, we create our empty pet store + store := api.NewPetStore() + + // Get the swagger description of our API + swagger, err := api.GetSwagger() + require.NoError(t, err) + + // This disables swagger server name validation. It seems to work poorly, + // and requires our test server to be in that list. + swagger.Servers = nil + + // Validate requests against the OpenAPI spec + app.Use(middleware.OapiRequestValidator(swagger)) + + // Log requests + app.Use(logger.New()) + + // We register the autogenerated boilerplate and bind our PetStore to this + // fiber router. + api.RegisterHandlers(app, store) + + // At this point, we can start sending simulated Http requests, and record + // the HTTP responses to check for validity. This exercises every part of + // the stack except the well-tested HTTP system in Go, which there is no + // point for us to test. + tag := "TagOfSpot" + newPet := models.NewPet{ + Name: "Spot", + Tag: &tag, + } + handler := adaptor.FiberApp(app) + result := testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, handler) + // We expect 201 code on successful pet insertion + assert.Equal(t, http.StatusCreated, result.Code()) + + // We should have gotten a response from the server with the new pet. Make + // sure that its fields match. + var resultPet models.Pet + err = result.UnmarshalBodyToObject(&resultPet) + assert.NoError(t, err, "error unmarshalling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + + // This is the Id of the pet we inserted. + petId := resultPet.Id + + // Test the getter function. + result = testutil.NewRequest().Get(fmt.Sprintf("/pets/%d", petId)).WithAcceptJson().GoWithHTTPHandler(t, handler) + var resultPet2 models.Pet + err = result.UnmarshalBodyToObject(&resultPet2) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, resultPet, resultPet2) + + // We should get a 404 on invalid ID + result = testutil.NewRequest().Get("/pets/27179095781").WithAcceptJson().GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusNotFound, result.Code()) + var petError models.Error + err = result.UnmarshalBodyToObject(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Let's insert another pet for subsequent tests. + tag = "TagOfFido" + newPet = models.NewPet{ + Name: "Fido", + Tag: &tag, + } + result = testutil.NewRequest().Post("/pets").WithJsonBody(newPet).GoWithHTTPHandler(t, handler) + // We expect 201 code on successful pet insertion + assert.Equal(t, http.StatusCreated, result.Code()) + // We should have gotten a response from the server with the new pet. Make + // sure that its fields match. + err = result.UnmarshalBodyToObject(&resultPet) + assert.NoError(t, err, "error unmarshalling response") + petId2 := resultPet.Id + + // Now, list all pets, we should have two + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusOK, result.Code()) + var petList []models.Pet + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + + // Filter pets by tag, we should have 1 + petList = nil + result = testutil.NewRequest().Get("/pets?tags=TagOfFido").WithAcceptJson().GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + + // Filter pets by non existent tag, we should have 0 + petList = nil + result = testutil.NewRequest().Get("/pets?tags=NotExists").WithAcceptJson().GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + + // Let's delete non-existent pet + result = testutil.NewRequest().Delete("/pets/7").GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusNotFound, result.Code()) + err = result.UnmarshalBodyToObject(&petError) + assert.NoError(t, err, "error unmarshalling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId)).GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusNoContent, result.Code()) + result = testutil.NewRequest().Delete(fmt.Sprintf("/pets/%d", petId2)).GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusNoContent, result.Code()) + + // Should have no pets left. + petList = nil + result = testutil.NewRequest().Get("/pets").WithAcceptJson().GoWithHTTPHandler(t, handler) + assert.Equal(t, http.StatusOK, result.Code()) + err = result.UnmarshalBodyToObject(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) +} diff --git a/go.mod b/go.mod index 31bc950e3e..7655b991e1 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( github.com/getkin/kin-openapi v0.115.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.0 + github.com/gofiber/fiber/v2 v2.43.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 +20,7 @@ require ( ) 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 +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.0.9 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -44,20 +48,28 @@ 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.17 // 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.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.9 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.45.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.9.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect diff --git a/go.sum b/go.sum index e2d021db56..5b3c045953 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.0 h1:MGz/LW8l+avBER56v87dzcH+mqi+90CX00k8Lv8QQz8= +github.com/gofiber/adaptor/v2 v2.2.0/go.mod h1:A51dt83PyWNUZp/9Op4FBI2qxDUceg15hWtf8Vk9ZOU= +github.com/gofiber/fiber/v2 v2.43.0 h1:yit3E4kHf178B60p5CQBa/3v+WVuziWMa/G2ZNyLJB0= +github.com/gofiber/fiber/v2 v2.43.0/go.mod h1:mpS1ZNE5jU+u+BA4FbM+KKnUzJ4wzTK+FT2tG3tU+6I= 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.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 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= @@ -100,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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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,21 @@ 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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 +151,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 +162,81 @@ 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.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.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.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.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.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +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.cfg.yaml b/internal/test/strict-server/fiber/server.cfg.yaml new file mode 100644 index 0000000000..3a6471261f --- /dev/null +++ b/internal/test/strict-server/fiber/server.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + fiber-server: 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..b0c6ac600c --- /dev/null +++ b/internal/test/strict-server/fiber/server.gen.go @@ -0,0 +1,1141 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by unknown module path version unknown version 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(ctx *fiber.Ctx) error + + // (Post /multipart) + MultipartExample(ctx *fiber.Ctx) error + + // (Post /multiple) + MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error + + // (Get /reserved-go-keyword-parameters/{type}) + ReservedGoKeywordParameters(ctx *fiber.Ctx, pType string) error + + // (Post /reusable-responses) + ReusableResponses(ctx *fiber.Ctx) error + + // (Post /text) + TextExample(ctx *fiber.Ctx) error + + // (Post /unknown) + UnknownExample(ctx *fiber.Ctx) error + + // (Post /unspecified-content-type) + UnspecifiedContentType(ctx *fiber.Ctx) error + + // (Post /urlencoded) + URLEncodedExample(ctx *fiber.Ctx) error + + // (Post /with-headers) + HeadersExample(ctx *fiber.Ctx, params HeadersExampleParams) error +} + +// ServerInterfaceWrapper converts fiber contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// JSONExample converts fiber context to params. +func (w *ServerInterfaceWrapper) JSONExample(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.JSONExample(ctx) + return err +} + +// MultipartExample converts fiber context to params. +func (w *ServerInterfaceWrapper) MultipartExample(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.MultipartExample(ctx) + return err +} + +// MultipleRequestAndResponseTypes converts fiber context to params. +func (w *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.MultipleRequestAndResponseTypes(ctx) + return err +} + +// ReservedGoKeywordParameters converts fiber context to params. +func (w *ServerInterfaceWrapper) ReservedGoKeywordParameters(ctx *fiber.Ctx) error { + var err error + // ------------- Path parameter "type" ------------- + var pType string + + err = runtime.BindStyledParameterWithLocation("simple", false, "type", runtime.ParamLocationPath, ctx.Params("type"), &pType) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter type: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.ReservedGoKeywordParameters(ctx, pType) + return err +} + +// ReusableResponses converts fiber context to params. +func (w *ServerInterfaceWrapper) ReusableResponses(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.ReusableResponses(ctx) + return err +} + +// TextExample converts fiber context to params. +func (w *ServerInterfaceWrapper) TextExample(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.TextExample(ctx) + return err +} + +// UnknownExample converts fiber context to params. +func (w *ServerInterfaceWrapper) UnknownExample(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UnknownExample(ctx) + return err +} + +// UnspecifiedContentType converts fiber context to params. +func (w *ServerInterfaceWrapper) UnspecifiedContentType(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UnspecifiedContentType(ctx) + return err +} + +// URLEncodedExample converts fiber context to params. +func (w *ServerInterfaceWrapper) URLEncodedExample(ctx *fiber.Ctx) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.URLEncodedExample(ctx) + return err +} + +// HeadersExample converts fiber context to params. +func (w *ServerInterfaceWrapper) HeadersExample(ctx *fiber.Ctx) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params HeadersExampleParams + + headers := http.Header{} + ctx.Request().Header.VisitAll(func(k, v []byte) { + key := string(k) + value := string(v) + headers.Add(key, value) + }) + + // ------------- Required header parameter "header1" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header1")]; found { + var Header1 string + n := len(valueList) + if n != 1 { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header1, got %d", n)) + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header1: %s", err)) + } + + params.Header1 = Header1 + } else { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Header parameter header1 is required, but not found")) + } + // ------------- Optional header parameter "header2" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("header2")]; found { + var Header2 int + n := len(valueList) + if n != 1 { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Expected one value for header2, got %d", n)) + } + + err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter header2: %s", err)) + } + + params.Header2 = &Header2 + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.HeadersExample(ctx, params) + return err +} + +// This is a simple interface which specifies Fiber.App addition functions which +// are present on both fiber.App and fiber.Router, since we want to allow using +// either of them for path registration +type FiberRouter interface { + Use(args ...interface{}) fiber.Router + + Get(path string, handlers ...fiber.Handler) fiber.Router + Head(path string, handlers ...fiber.Handler) fiber.Router + Post(path string, handlers ...fiber.Handler) fiber.Router + Put(path string, handlers ...fiber.Handler) fiber.Router + Delete(path string, handlers ...fiber.Handler) fiber.Router + Connect(path string, handlers ...fiber.Handler) fiber.Router + Options(path string, handlers ...fiber.Handler) fiber.Router + Trace(path string, handlers ...fiber.Handler) fiber.Router + Patch(path string, handlers ...fiber.Handler) fiber.Router + + Add(method, path string, handlers ...fiber.Handler) fiber.Router + Static(prefix, root string, config ...fiber.Static) fiber.Router + All(path string, handlers ...fiber.Handler) fiber.Router + + Group(prefix string, handlers ...fiber.Handler) fiber.Router + + Route(prefix string, fn func(router fiber.Router), name ...string) fiber.Router + + Mount(prefix string, fiber *fiber.App) fiber.Router + + Name(name string) fiber.Router +} + +// RegisterHandlers adds each server route to the FiberRouter. +func RegisterHandlers(router FiberRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router FiberRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.Post(baseURL+"/json", wrapper.JSONExample) + router.Post(baseURL+"/multipart", wrapper.MultipartExample) + router.Post(baseURL+"/multiple", wrapper.MultipleRequestAndResponseTypes) + router.Get(baseURL+"/reserved-go-keyword-parameters/:type", wrapper.ReservedGoKeywordParameters) + router.Post(baseURL+"/reusable-responses", wrapper.ReusableResponses) + router.Post(baseURL+"/text", wrapper.TextExample) + router.Post(baseURL+"/unknown", wrapper.UnknownExample) + router.Post(baseURL+"/unspecified-content-type", wrapper.UnspecifiedContentType) + router.Post(baseURL+"/urlencoded", wrapper.URLEncodedExample) + router.Post(baseURL+"/with-headers", wrapper.HeadersExample) + +} + +type BadrequestResponse struct { +} + +type ReusableresponseResponseHeaders struct { + Header1 string + Header2 int +} +type ReusableresponseJSONResponse struct { + Body Example + + Headers ReusableresponseResponseHeaders +} + +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([]byte(response))) + return err +} + +type MultipleRequestAndResponseTypes400Response = BadrequestResponse + +func (response MultipleRequestAndResponseTypes400Response) VisitMultipleRequestAndResponseTypesResponse(ctx *fiber.Ctx) error { + ctx.Status(400) + 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([]byte(response))) + return err +} + +type ReusableResponsesRequestObject struct { + Body *ReusableResponsesJSONRequestBody +} + +type ReusableResponsesResponseObject interface { + VisitReusableResponsesResponse(ctx *fiber.Ctx) error +} + +type ReusableResponses200JSONResponse struct{ 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([]byte(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) + + // (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) + + // (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 err + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.JSONExample(ctx.Context(), request.(JSONExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "JSONExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(JSONExampleResponseObject); ok { + return validResponse.VisitJSONExampleResponse(ctx) + } 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 + + b := ctx.Request().Body() + boundary := string(ctx.Request().Header.MultipartFormBoundary()) + request.Body = multipart.NewReader(bytes.NewReader(b), boundary) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipartExample(ctx.Context(), request.(MultipartExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipartExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(MultipartExampleResponseObject); ok { + return validResponse.VisitMultipartExampleResponse(ctx) + } 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(ctx.Get("Content-Type"), "application/json") { + var body MultipleRequestAndResponseTypesJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return err + } + request.JSONBody = &body + } + if strings.HasPrefix(ctx.Get("Content-Type"), "application/x-www-form-urlencoded") { + var body MultipleRequestAndResponseTypesFormdataRequestBody + if err := ctx.BodyParser(&body); err == nil { + request.FormdataBody = &body + } else { + return err + } + } + if strings.HasPrefix(ctx.Get("Content-Type"), "image/png") { + request.Body = bytes.NewReader(ctx.Request().Body()) + } + if strings.HasPrefix(ctx.Get("Content-Type"), "multipart/form-data") { + b := ctx.Request().Body() + boundary := string(ctx.Request().Header.MultipartFormBoundary()) + request.MultipartBody = multipart.NewReader(bytes.NewReader(b), boundary) + } + if strings.HasPrefix(ctx.Get("Content-Type"), "text/plain") { + data, err := io.ReadAll(bytes.NewReader(ctx.Request().Body())) + if err != nil { + return err + } + body := MultipleRequestAndResponseTypesTextRequestBody(data) + request.TextBody = &body + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.MultipleRequestAndResponseTypes(ctx.Context(), request.(MultipleRequestAndResponseTypesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "MultipleRequestAndResponseTypes") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(MultipleRequestAndResponseTypesResponseObject); ok { + return validResponse.VisitMultipleRequestAndResponseTypesResponse(ctx) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// ReservedGoKeywordParameters operation middleware +func (sh *strictHandler) ReservedGoKeywordParameters(ctx *fiber.Ctx, pType string) error { + var request ReservedGoKeywordParametersRequestObject + + request.Type = pType + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReservedGoKeywordParameters(ctx.Context(), request.(ReservedGoKeywordParametersRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReservedGoKeywordParameters") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ReservedGoKeywordParametersResponseObject); ok { + return validResponse.VisitReservedGoKeywordParametersResponse(ctx) + } 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 err + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ReusableResponses(ctx.Context(), request.(ReusableResponsesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReusableResponses") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ReusableResponsesResponseObject); ok { + return validResponse.VisitReusableResponsesResponse(ctx) + } 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, err := io.ReadAll(bytes.NewReader(ctx.Request().Body())) + if err != nil { + return err + } + body := TextExampleTextRequestBody(data) + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.TextExample(ctx.Context(), request.(TextExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "TextExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(TextExampleResponseObject); ok { + return validResponse.VisitTextExampleResponse(ctx) + } 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.Context(), request.(UnknownExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnknownExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(UnknownExampleResponseObject); ok { + return validResponse.VisitUnknownExampleResponse(ctx) + } 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 = ctx.Get("Content-Type") + + request.Body = bytes.NewReader(ctx.Request().Body()) + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UnspecifiedContentType(ctx.Context(), request.(UnspecifiedContentTypeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UnspecifiedContentType") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(UnspecifiedContentTypeResponseObject); ok { + return validResponse.VisitUnspecifiedContentTypeResponse(ctx) + } 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 { + request.Body = &body + } else { + return err + } + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.URLEncodedExample(ctx.Context(), request.(URLEncodedExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "URLEncodedExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(URLEncodedExampleResponseObject); ok { + return validResponse.VisitURLEncodedExampleResponse(ctx) + } 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 err + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.HeadersExample(ctx.Context(), request.(HeadersExampleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "HeadersExample") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(HeadersExampleResponseObject); ok { + return validResponse.VisitHeadersExampleResponse(ctx) + } 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/+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 +// 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..b0724db19f --- /dev/null +++ b/internal/test/strict-server/fiber/server.go @@ -0,0 +1,106 @@ +//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 + } 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 + } + } + }), nil +} + +func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) { + switch { + case request.Body != nil: + return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, 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 + } 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 + } + } + }), 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}, nil +} + +func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) { + return UnspecifiedContentType200VideoResponse{Body: request.Body, ContentType: request.ContentType}, 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{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil +} + +func (s StrictServer) ReservedGoKeywordParameters(ctx context.Context, request ReservedGoKeywordParametersRequestObject) (ReservedGoKeywordParametersResponseObject, error) { + return ReservedGoKeywordParameters200TextResponse(""), 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..23225a16b1 --- /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 unknown module path version unknown version 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 860cc4cb8d..4cd31a09c5 100644 --- a/internal/test/strict-server/strict_test.go +++ b/internal/test/strict-server/strict_test.go @@ -13,12 +13,15 @@ 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" + api "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" + api5 "github.com/deepmap/oapi-codegen/internal/test/strict-server/fiber" api2 "github.com/deepmap/oapi-codegen/internal/test/strict-server/gin" "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/deepmap/oapi-codegen/pkg/testutil" @@ -40,6 +43,14 @@ func TestEchoServer(t *testing.T) { testImpl(t, e) } +func TestFiberServer(t *testing.T) { + server := api5.StrictServer{} + strictHandler := api5.NewStrictHandler(server, nil) + app := fiber.New() + api5.RegisterHandlers(app, strictHandler) + testImpl(t, adaptor.FiberApp(app)) +} + func TestGinServer(t *testing.T) { server := api2.StrictServer{} strictHandler := api2.NewStrictHandler(server, nil) diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 956d589329..c4e7b03356 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -183,6 +183,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) @@ -292,6 +300,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 afa4fc5020..e4be3af29b 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -24,6 +24,7 @@ type Configuration struct { type GenerateOptions struct { ChiServer bool `yaml:"chi-server,omitempty"` // ChiServer specifies whether to generate chi server boilerplate EchoServer bool `yaml:"echo-server,omitempty"` // EchoServer specifies whether to generate echo server boilerplate + FiberServer bool `yaml:"fiber-server,omitempty"` // FiberServer specifies whether to generate fiber 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 Strict bool `yaml:"strict-server,omitempty"` // Strict specifies whether to generate strict server wrapper @@ -115,6 +116,9 @@ func (o Configuration) Validate() error { if o.Generate.EchoServer { nServers++ } + if o.Generate.FiberServer { + nServers++ + } if o.Generate.GinServer { nServers++ } diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 28b991f904..493fd9238b 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -908,6 +908,17 @@ func GenerateEchoServer(t *template.Template, operations []OperationDefinition) return GenerateTemplates([]string{"echo/echo-interface.tmpl", "echo/echo-wrappers.tmpl", "echo/echo-register.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) { + for index := range operations { + m := operations[index].Method + operations[index].Method = fmt.Sprintf("%s%s", strings.ToUpper(m[:1]), strings.ToLower(m[1:])) + } + + return GenerateTemplates([]string{"fiber/fiber-interface.tmpl", "fiber/fiber-wrappers.tmpl", "fiber/fiber-register.tmpl"}, t, operations) +} + // GenerateGinServer generates all the go code for the ServerInterface as well as // all the wrapper functions around our handlers. func GenerateGinServer(t *template.Template, operations []OperationDefinition) (string, error) { @@ -928,6 +939,10 @@ func GenerateStrictServer(t *template.Template, operations []OperationDefinition if opts.Generate.EchoServer { templates = append(templates, "strict/strict-echo.tmpl") } + if opts.Generate.FiberServer { + templates[0] = "strict/strict-interface-fiber.tmpl" + templates = append(templates, "strict/strict-fiber.tmpl") + } if opts.Generate.GinServer { templates = append(templates, "strict/strict-gin.tmpl") } diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index 16671fb393..1986ed063e 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -284,6 +284,7 @@ var TemplateFunctions = template.FuncMap{ "genParamFmtString": ReplacePathParamsWithStr, "swaggerUriToEchoUri": SwaggerUriToEchoUri, "swaggerUriToChiUri": SwaggerUriToChiUri, + "swaggerUriToFiberUri": SwaggerUriToFiberUri, "swaggerUriToGinUri": SwaggerUriToGinUri, "swaggerUriToGorillaUri": SwaggerUriToGorillaUri, "lcFirst": LowercaseFirstCharacter, diff --git a/pkg/codegen/templates/fiber/fiber-interface.tmpl b/pkg/codegen/templates/fiber/fiber-interface.tmpl new file mode 100644 index 0000000000..38e0fa58a1 --- /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}}(ctx *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error +{{end}} +} diff --git a/pkg/codegen/templates/fiber/fiber-register.tmpl b/pkg/codegen/templates/fiber/fiber-register.tmpl new file mode 100644 index 0000000000..7bbb36185e --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-register.tmpl @@ -0,0 +1,47 @@ + + +// This is a simple interface which specifies Fiber.App addition functions which +// are present on both fiber.App and fiber.Router, since we want to allow using +// either of them for path registration +type FiberRouter interface { + Use(args ...interface{}) fiber.Router + + Get(path string, handlers ...fiber.Handler) fiber.Router + Head(path string, handlers ...fiber.Handler) fiber.Router + Post(path string, handlers ...fiber.Handler) fiber.Router + Put(path string, handlers ...fiber.Handler) fiber.Router + Delete(path string, handlers ...fiber.Handler) fiber.Router + Connect(path string, handlers ...fiber.Handler) fiber.Router + Options(path string, handlers ...fiber.Handler) fiber.Router + Trace(path string, handlers ...fiber.Handler) fiber.Router + Patch(path string, handlers ...fiber.Handler) fiber.Router + + Add(method, path string, handlers ...fiber.Handler) fiber.Router + Static(prefix, root string, config ...fiber.Static) fiber.Router + All(path string, handlers ...fiber.Handler) fiber.Router + + Group(prefix string, handlers ...fiber.Handler) fiber.Router + + Route(prefix string, fn func(router fiber.Router), name ...string) fiber.Router + + Mount(prefix string, fiber *fiber.App) fiber.Router + + Name(name string) fiber.Router +} + +// RegisterHandlers adds each server route to the FiberRouter. +func RegisterHandlers(router FiberRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router FiberRouter, si ServerInterface, baseURL string) { +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}router.{{.Method}}(baseURL + "{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}}) +{{end}} +} diff --git a/pkg/codegen/templates/fiber/fiber-wrappers.tmpl b/pkg/codegen/templates/fiber/fiber-wrappers.tmpl new file mode 100644 index 0000000000..91c4e7cc9e --- /dev/null +++ b/pkg/codegen/templates/fiber/fiber-wrappers.tmpl @@ -0,0 +1,148 @@ +// ServerInterfaceWrapper converts fiber contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}}// {{$opid}} converts fiber context to params. +func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx *fiber.Ctx) error { + var err error +{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} +{{if .IsPassThrough}} + {{$varName}} = ctx.Params("{{.ParamName}}") +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(ctx.Params("{{.ParamName}}")), &{{$varName}}) + if err != nil { + return fiber.NewError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, ctx.Params("{{.ParamName}}"), &{{$varName}}) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} +{{end}} + +{{range .SecurityDefinitions}} + ctx.Locals({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}}) +{{end}} + +{{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + +{{if .QueryParams}} + bURL := ctx.Request().URI().FullURI() + sURL := string(bURL) + u, err := url.Parse(sURL) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameters: %s", err)) + } +{{end}} + +{{range $paramIdx, $param := .QueryParams}} + {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}} + // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{ end }} + {{if .IsStyled}} + + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", u.Query(), ¶ms.{{.GoName}}) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + {{else}} + if paramValue := ctx.Query("{{.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 { + return fiber.NewError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + {{end}} +{{end}} + +{{if .HeaderParams}} + headers := http.Header{} + ctx.Request().Header.VisitAll(func(k, v []byte) { + key := string(k) + value := string(v) + headers.Add(key, value) + }) + +{{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 { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) + } +{{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + return fiber.NewError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + } {{if .Required}}else { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) + }{{end}} +{{end}} +{{end}} + +{{range .CookieParams}} + if v := ctx.Cookies("{{.ParamName}}"); v == "" { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}v + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(v) + if err != nil { + return fiber.NewError(http.StatusBadRequest, "Error unescaping cookie parameter '{{.ParamName}}'") + } + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + return fiber.NewError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + {{if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameterWithLocation("simple",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, v, &value) + if err != nil { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return fiber.NewError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + +{{end}}{{/* .CookieParams */}} + +{{end}}{{/* .RequiresParamObject */}} + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + return err +} +{{end}} diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl index ef43ef390f..8a7ae0d745 100644 --- a/pkg/codegen/templates/imports.tmpl +++ b/pkg/codegen/templates/imports.tmpl @@ -25,6 +25,7 @@ import ( openapi_types "github.com/deepmap/oapi-codegen/pkg/types" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/gofiber/fiber/v2" "github.com/labstack/echo/v4" "github.com/gin-gonic/gin" "github.com/gorilla/mux" diff --git a/pkg/codegen/templates/strict/strict-fiber.tmpl b/pkg/codegen/templates/strict/strict-fiber.tmpl new file mode 100644 index 0000000000..ba81014a45 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-fiber.tmpl @@ -0,0 +1,83 @@ +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 +} + +{{range .}} + {{$opid := .OperationId}} + // {{$opid}} operation middleware + func (sh *strictHandler) {{.OperationId}}(ctx *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error { + var request {{$opid | ucFirst}}RequestObject + + {{range .PathParams -}} + request.{{.GoName}} = {{.GoVariableName}} + {{end -}} + + {{if .RequiresParamObject -}} + request.Params = params + {{end -}} + + {{ if .HasMaskedRequestContentTypes -}} + request.ContentType = ctx.Get("Content-Type") + {{end -}} + + {{$multipleBodies := gt (len .Bodies) 1 -}} + {{range .Bodies -}} + {{if $multipleBodies}}if strings.HasPrefix(ctx.Get("Content-Type"), "{{.ContentType}}") { {{end}} + {{if eq .NameTag "JSON" -}} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.BodyParser(&body); err != nil { + return err + } + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else if eq .NameTag "Formdata" -}} + var body {{$opid}}{{.NameTag}}RequestBody + if err := ctx.BodyParser(&body); err == nil { + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + } else { + return err + } + {{else if eq .NameTag "Multipart" -}} + b := ctx.Request().Body() + boundary := string(ctx.Request().Header.MultipartFormBoundary()) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(b), boundary) + {{else if eq .NameTag "Text" -}} + data, err := io.ReadAll(bytes.NewReader(ctx.Request().Body())) + if err != nil { + return err + } + body := {{$opid}}{{.NameTag}}RequestBody(data) + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body + {{else -}} + request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = bytes.NewReader(ctx.Request().Body()) + {{end}}{{/* if eq .NameTag "JSON" */ -}} + {{if $multipleBodies}}}{{end}} + {{end}}{{/* range .Bodies */}} + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error){ + return sh.ssi.{{.OperationId}}(ctx.Context(), request.({{$opid | ucFirst}}RequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "{{.OperationId}}") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok { + return validResponse.Visit{{$opid}}Response(ctx) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil + } +{{end}} diff --git a/pkg/codegen/templates/strict/strict-interface-fiber.tmpl b/pkg/codegen/templates/strict/strict-interface-fiber.tmpl new file mode 100644 index 0000000000..92ef9034a5 --- /dev/null +++ b/pkg/codegen/templates/strict/strict-interface-fiber.tmpl @@ -0,0 +1,137 @@ +{{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 | ucFirstWithPkgName -}} + {{$headers := .Headers -}} + + {{if (and $hasHeaders (not $isRef)) -}} + type {{$opid}}{{$statusCode}}ResponseHeaders struct { + {{range .Headers -}} + {{.GoName}} {{.Schema.TypeDecl}} + {{end -}} + } + {{end}} + + {{range .Contents}} + {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} + {{if and $fixedStatusCode $isRef -}} + type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response } + {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} + 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 {{$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 + {{end -}} + + {{if not $fixedStatusCode -}} + StatusCode int + {{end -}} + + {{if not .HasFixedContentType -}} + ContentType string + {{end -}} + + {{if not .IsSupported -}} + ContentLength int64 + {{end -}} + } + {{end}} + + func (response {{$receiverTypeName}}) 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([]byte({{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/fiber-middleware/oapi_validate.go b/pkg/fiber-middleware/oapi_validate.go new file mode 100644 index 0000000000..1a79aae013 --- /dev/null +++ b/pkg/fiber-middleware/oapi_validate.go @@ -0,0 +1,236 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "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" +) + +const ( + FiberContextKey = "oapi-codegen/fiber-context" + UserDataKey = "oapi-codegen/user-data" +) + +// OapiValidatorFromYamlFile is an 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. +// Create 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 creates a validator from a swagger object. +func OapiRequestValidator(swagger *openapi3.T) fiber.Handler { + return OapiRequestValidatorWithOptions(swagger, nil) +} + +// ErrorHandler is called when there is an error in validation +type ErrorHandler func(ctx *fiber.Ctx, err *fiber.Error) error + +// MultiErrorHandler is called when oapi returns a MultiError type +type MultiErrorHandler func(openapi3.MultiError) *fiber.Error + +// Options to customize request validation. These are passed through to +// openapi3filter. +type Options struct { + ErrorHandler ErrorHandler + Options openapi3filter.Options + ParamDecoder openapi3filter.ContentParameterDecoder + UserData interface{} + MultiErrorHandler MultiErrorHandler + // SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil` + SilenceServersWarning bool +} + +// OapiRequestValidatorWithOptions creates a validator from a swagger object, with validation options +func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) fiber.Handler { + if swagger.Servers != nil && (options == nil || options.SilenceServersWarning) { + log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.") + } + + router, err := gorillamux.NewRouter(swagger) + if err != nil { + panic(err) + } + + return func(ctx *fiber.Ctx) error { + err := ValidateRequestFromContext(ctx, router, options) + if err != nil { + if options != nil && options.ErrorHandler != nil { + return options.ErrorHandler(ctx, err) + + } + + return err + } + + return ctx.Next() + } +} + +// ValidateRequestFromContext is called from the middleware above and actually does the work +// of validating a request. +func ValidateRequestFromContext(ctx *fiber.Ctx, router routers.Router, options *Options) *fiber.Error { + r := ctx.Request() + method := string(r.Header.Method()) + body := bytes.NewReader(r.Body()) + req, err := http.NewRequest(method, r.URI().String(), body) + + for key, value := range ctx.GetReqHeaders() { + req.Header.Add(key, value) + } + + if err != nil { + return fiber.NewError(http.StatusBadRequest, err.Error()) + } + + route, pathParams, err := router.FindRoute(req) + + // We failed to find a matching route for the request. + if err != nil { + 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 fiber.NewError(http.StatusBadRequest, 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 fiber.NewError(http.StatusInternalServerError, + fmt.Sprintf("error validating route: %s", err.Error())) + } + } + + validationInput := &openapi3filter.RequestValidationInput{ + Request: req, + PathParams: pathParams, + Route: route, + } + + // Pass the Fiber context into the request validator, so that any callbacks + // which it invokes make it available. + requestContext := context.WithValue(context.Background(), FiberContextKey, ctx) //nolint:staticcheck + + if options != nil { + validationInput.Options = &options.Options + validationInput.ParamDecoder = options.ParamDecoder + requestContext = context.WithValue(requestContext, UserDataKey, options.UserData) //nolint:staticcheck + } + + err = openapi3filter.ValidateRequest(requestContext, validationInput) + if 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 &fiber.Error{ + Code: http.StatusBadRequest, + Message: errorLines[0], + } + case *openapi3filter.SecurityRequirementsError: + for _, err := range e.Errors { + httpErr, ok := err.(*fiber.Error) + if ok { + return httpErr + } + } + return &fiber.Error{ + Code: http.StatusForbidden, + Message: 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 &fiber.Error{ + Code: http.StatusInternalServerError, + Message: fmt.Sprintf("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(FiberContextKey) + if iface == nil { + return nil + } + fCtx, ok := iface.(*fiber.Ctx) + if !ok { + return nil + } + return fCtx +} + +func GetUserData(c context.Context) interface{} { + return c.Value(UserDataKey) +} + +// 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 of the errors. This method is called if there are no other +// methods defined on the options. +func defaultMultiErrorHandler(me openapi3.MultiError) *fiber.Error { + return &fiber.Error{ + Code: http.StatusBadRequest, + Message: me.Error(), + } +} diff --git a/pkg/fiber-middleware/oapi_validate_test.go b/pkg/fiber-middleware/oapi_validate_test.go new file mode 100644 index 0000000000..86889e9caf --- /dev/null +++ b/pkg/fiber-middleware/oapi_validate_test.go @@ -0,0 +1,419 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +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/adaptor/v2" + "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, app *fiber.App, rawURL string) *httptest.ResponseRecorder { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + handler := adaptor.FiberApp(app) + + response := testutil.NewRequest().Get(u.RequestURI()).WithHost(u.Host).WithAcceptJson().GoWithHTTPHandler(t, handler) + return response.Recorder +} + +func doPost(t *testing.T, app *fiber.App, rawURL string, jsonBody interface{}) *httptest.ResponseRecorder { + u, err := url.Parse(rawURL) + if err != nil { + t.Fatalf("Invalid url: %s", rawURL) + } + + handler := adaptor.FiberApp(app) + + response := testutil.NewRequest().Post(u.RequestURI()).WithHost(u.Host).WithJsonBody(jsonBody).GoWithHTTPHandler(t, handler) + return response.Recorder +} + +func TestOapiRequestValidator(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + // Create a new fiber app + a := fiber.New() + + // Set up an authenticator to check authenticated function. It will allow + // access to "someScope", but disallow others. + options := Options{ + ErrorHandler: func(ctx *fiber.Ctx, err *fiber.Error) error { + return ctx.Status(err.Code).SendString("test: " + err.Error()) + }, + Options: openapi3filter.Options{ + AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error { + // The fiber context should be propagated into here. + eCtx := GetFiberContext(c) + assert.NotNil(t, eCtx) + // As should user data + assert.EqualValues(t, "hi!", GetUserData(c)) + + for _, s := range input.Scopes { + if s == "someScope" { + return nil + } + if s == "unauthorized" { + return fiber.ErrUnauthorized + } + } + return errors.New("forbidden") + }, + }, + UserData: "hi!", + } + + // Install our OpenApi based request validator + a.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + a.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, a, "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, a, "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, a, "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, a, "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 + a.Post("/resource", func(ctx *fiber.Ctx) error { + called = true + return ctx.SendStatus(http.StatusNoContent) + }) + + called = false + // Send a good request body + { + body := struct { + Name string `json:"name"` + }{ + Name: "Marcin", + } + rec := doPost(t, a, "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, a, "http://deepmap.ai/resource", body) + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + + a.Get("/protected_resource", func(ctx *fiber.Ctx) error { + called = true + return ctx.SendStatus(http.StatusNoContent) + + }) + + // Call a protected function to which we have access + { + rec := doGet(t, a, "http://deepmap.ai/protected_resource") + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.True(t, called, "Handler should have been called") + called = false + } + + a.Get("/protected_resource2", func(ctx *fiber.Ctx) error { + called = true + return ctx.SendStatus(http.StatusNoContent) + }) + // Call a protected function to which we dont have access + { + rec := doGet(t, a, "http://deepmap.ai/protected_resource2") + assert.Equal(t, http.StatusForbidden, rec.Code) + assert.False(t, called, "Handler should not have been called") + called = false + } + + a.Get("/protected_resource_401", func(ctx *fiber.Ctx) error { + called = true + return ctx.SendStatus(http.StatusNoContent) + }) + // Call a protected function without credentials + { + rec := doGet(t, a, "http://deepmap.ai/protected_resource_401") + assert.Equal(t, http.StatusUnauthorized, rec.Code) + assert.Equal(t, "test: Unauthorized", rec.Body.String()) + assert.False(t, called, "Handler should not have been called") + called = false + } +} + +func TestOapiRequestValidatorWithOptionsMultiError(t *testing.T) { + swagger, err := openapi3.NewLoader().LoadFromData(testSchema) + require.NoError(t, err, "Error initializing swagger") + + // Create a new fiber app + a := 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 + a.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + a.Get("/multiparamresource", func(_ *fiber.Ctx) error { + called = true + return nil + }) + + // Let's send a good request, it should pass + { + rec := doGet(t, a, "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, a, "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 a 2 missing parameters, it should return + // // a bad status + { + rec := doGet(t, a, "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, a, "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, a, "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") + + // Create a new fiber app + a := 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) *fiber.Error { + return &fiber.Error{ + Code: http.StatusTeapot, + Message: me.Error(), + } + }, + } + + // register middleware + a.Use(OapiRequestValidatorWithOptions(swagger, &options)) + + called := false + + // Install a request handler for /resource. We want to make sure it doesn't + // get called. + a.Get("/multiparamresource", func(_ *fiber.Ctx) error { + called = true + return nil + }) + + // Let's send a good request, it should pass + { + rec := doGet(t, a, "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, a, "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, a, "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, a, "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, a, "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 + } +} diff --git a/pkg/fiber-middleware/test_spec.yaml b/pkg/fiber-middleware/test_spec.yaml new file mode 100644 index 0000000000..1f847d756a --- /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