diff --git a/README.md b/README.md index a260248db..cb9743f1f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +--- +noteId: "f6113120b9f511eba0c64ffabff3407d" +tags: [] + +--- + OpenAPI Client and Server Code Generator ---------------------------------------- diff --git a/examples/oapi-codegen/api/petstore-expanded.yaml b/examples/oapi-codegen/api/petstore-expanded.yaml new file mode 100644 index 000000000..bf1187ace --- /dev/null +++ b/examples/oapi-codegen/api/petstore-expanded.yaml @@ -0,0 +1,164 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification + termsOfService: http://swagger.io/terms/ + contact: + name: Swagger API Team + email: apiteam@swagger.io + url: http://swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: http://petstore.swagger.io/api +paths: + /pets: + get: + summary: Returns all pets + description: | + Returns all pets from the system that the user has access to + Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + + Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + style: form + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + summary: Creates a new pet + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{id}: + get: + summary: Returns a pet by ID + description: Returns a pet based on a single ID + operationId: find pet by id + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + summary: Deletes a pet by ID + description: deletes a single pet based on the ID supplied + operationId: deletePet + parameters: + - name: id + in: path + description: ID of pet to delete + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: pet deleted + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + schemas: + Pet: + allOf: + - $ref: '#/components/schemas/NewPet' + - required: + - id + properties: + id: + type: integer + format: int64 + description: Unique id of the pet + + NewPet: + required: + - name + properties: + name: + type: string + description: Name of the pet + tag: + type: string + description: Type of the pet + + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + description: Error code + message: + type: string + description: Error message diff --git a/examples/oapi-codegen/api/petstore.gen.go b/examples/oapi-codegen/api/petstore.gen.go new file mode 100644 index 000000000..76eb4860f --- /dev/null +++ b/examples/oapi-codegen/api/petstore.gen.go @@ -0,0 +1,828 @@ +// Package petstore provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package petstore + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// Error defines model for Error. +type Error struct { + + // Error code + Code int32 `json:"code"` + + // Error message + Message string `json:"message"` +} + +// NewPet defines model for NewPet. +type NewPet struct { + + // Name of the pet + Name string `json:"name"` + + // Type of the pet + Tag *string `json:"tag,omitempty"` +} + +// Pet defines model for Pet. +type Pet struct { + // Embedded struct due to allOf(#/components/schemas/NewPet) + NewPet + // Embedded fields due to inline allOf schema + + // Unique id of the pet + Id int64 `json:"id"` +} + +// FindPetsParams defines parameters for FindPets. +type FindPetsParams struct { + + // tags to filter by + Tags *[]string `json:"tags,omitempty"` + + // maximum number of results to return + Limit *int32 `json:"limit,omitempty"` +} + +// AddPetJSONBody defines parameters for AddPet. +type AddPetJSONBody NewPet + +// AddPetRequestBody defines body for AddPet for application/json ContentType. +type AddPetJSONRequestBody AddPetJSONBody + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(req *http.Request, ctx context.Context) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A callback for modifying requests which are generated before sending over + // the network. + RequestEditor RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = http.DefaultClient + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditor = fn + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // FindPets request + FindPets(ctx context.Context, params *FindPetsParams) (*http.Response, error) + + // AddPet request with any body + AddPetWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error) + + AddPet(ctx context.Context, body AddPetJSONRequestBody) (*http.Response, error) + + // DeletePet request + DeletePet(ctx context.Context, id int64) (*http.Response, error) + + // FindPetById request + FindPetById(ctx context.Context, id int64) (*http.Response, error) +} + +func (c *Client) FindPets(ctx context.Context, params *FindPetsParams) (*http.Response, error) { + req, err := NewFindPetsRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(req, ctx) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) AddPetWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error) { + req, err := NewAddPetRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(req, ctx) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) AddPet(ctx context.Context, body AddPetJSONRequestBody) (*http.Response, error) { + req, err := NewAddPetRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(req, ctx) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) DeletePet(ctx context.Context, id int64) (*http.Response, error) { + req, err := NewDeletePetRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(req, ctx) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) FindPetById(ctx context.Context, id int64) (*http.Response, error) { + req, err := NewFindPetByIdRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(req, ctx) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +// NewFindPetsRequest generates requests for FindPets +func NewFindPetsRequest(server string, params *FindPetsParams) (*http.Request, error) { + var err error + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + queryUrl, err = queryUrl.Parse(fmt.Sprintf("/pets")) + if err != nil { + return nil, err + } + + queryValues := queryUrl.Query() + + if params.Tags != nil { + + if queryFrag, err := runtime.StyleParam("form", true, "tags", *params.Tags); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParam("form", true, "limit", *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryUrl.RawQuery = queryValues.Encode() + + req, err := http.NewRequest("GET", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewAddPetRequest calls the generic AddPet builder with application/json body +func NewAddPetRequest(server string, body AddPetJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAddPetRequestWithBody(server, "application/json", bodyReader) +} + +// NewAddPetRequestWithBody generates requests for AddPet with any type of body +func NewAddPetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + queryUrl, err = queryUrl.Parse(fmt.Sprintf("/pets")) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryUrl.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + return req, nil +} + +// NewDeletePetRequest generates requests for DeletePet +func NewDeletePetRequest(server string, id int64) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "id", id) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + queryUrl, err = queryUrl.Parse(fmt.Sprintf("/pets/%s", pathParam0)) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewFindPetByIdRequest generates requests for FindPetById +func NewFindPetByIdRequest(server string, id int64) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "id", id) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + queryUrl, err = queryUrl.Parse(fmt.Sprintf("/pets/%s", pathParam0)) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + if !strings.HasSuffix(baseURL, "/") { + baseURL += "/" + } + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +type findPetsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Pet + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r findPetsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r findPetsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type addPetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r addPetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r addPetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type deletePetResponse struct { + Body []byte + HTTPResponse *http.Response + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r deletePetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r deletePetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type findPetByIdResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Pet + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r findPetByIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r findPetByIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// FindPetsWithResponse request returning *FindPetsResponse +func (c *ClientWithResponses) FindPetsWithResponse(ctx context.Context, params *FindPetsParams) (*findPetsResponse, error) { + rsp, err := c.FindPets(ctx, params) + if err != nil { + return nil, err + } + return ParseFindPetsResponse(rsp) +} + +// AddPetWithBodyWithResponse request with arbitrary body returning *AddPetResponse +func (c *ClientWithResponses) AddPetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*addPetResponse, error) { + rsp, err := c.AddPetWithBody(ctx, contentType, body) + if err != nil { + return nil, err + } + return ParseAddPetResponse(rsp) +} + +func (c *ClientWithResponses) AddPetWithResponse(ctx context.Context, body AddPetJSONRequestBody) (*addPetResponse, error) { + rsp, err := c.AddPet(ctx, body) + if err != nil { + return nil, err + } + return ParseAddPetResponse(rsp) +} + +// DeletePetWithResponse request returning *DeletePetResponse +func (c *ClientWithResponses) DeletePetWithResponse(ctx context.Context, id int64) (*deletePetResponse, error) { + rsp, err := c.DeletePet(ctx, id) + if err != nil { + return nil, err + } + return ParseDeletePetResponse(rsp) +} + +// FindPetByIdWithResponse request returning *FindPetByIdResponse +func (c *ClientWithResponses) FindPetByIdWithResponse(ctx context.Context, id int64) (*findPetByIdResponse, error) { + rsp, err := c.FindPetById(ctx, id) + if err != nil { + return nil, err + } + return ParseFindPetByIdResponse(rsp) +} + +// ParseFindPetsResponse parses an HTTP response from a FindPetsWithResponse call +func ParseFindPetsResponse(rsp *http.Response) (*findPetsResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &findPetsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + response.JSON200 = &[]Pet{} + if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil { + return nil, err + } + + case strings.Contains(rsp.Header.Get("Content-Type"), "json"): + response.JSONDefault = &Error{} + if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil { + return nil, err + } + + } + + return response, nil +} + +// ParseAddPetResponse parses an HTTP response from a AddPetWithResponse call +func ParseAddPetResponse(rsp *http.Response) (*addPetResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &addPetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + response.JSON200 = &Pet{} + if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil { + return nil, err + } + + case strings.Contains(rsp.Header.Get("Content-Type"), "json"): + response.JSONDefault = &Error{} + if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil { + return nil, err + } + + } + + return response, nil +} + +// ParseDeletePetResponse parses an HTTP response from a DeletePetWithResponse call +func ParseDeletePetResponse(rsp *http.Response) (*deletePetResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &deletePetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json"): + response.JSONDefault = &Error{} + if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil { + return nil, err + } + + } + + return response, nil +} + +// ParseFindPetByIdResponse parses an HTTP response from a FindPetByIdWithResponse call +func ParseFindPetByIdResponse(rsp *http.Response) (*findPetByIdResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &findPetByIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + response.JSON200 = &Pet{} + if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil { + return nil, err + } + + case strings.Contains(rsp.Header.Get("Content-Type"), "json"): + response.JSONDefault = &Error{} + if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil { + return nil, err + } + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Returns all pets + // (GET /pets) + FindPets(ctx echo.Context, params FindPetsParams) error + // Creates a new pet + // (POST /pets) + AddPet(ctx echo.Context) error + // Deletes a pet by ID + // (DELETE /pets/{id}) + DeletePet(ctx echo.Context, id int64) error + // Returns a pet by ID + // (GET /pets/{id}) + FindPetById(ctx echo.Context, id int64) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// FindPets converts echo context to params. +func (w *ServerInterfaceWrapper) FindPets(ctx echo.Context) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params FindPetsParams + // ------------- Optional query parameter "tags" ------------- + if paramValue := ctx.QueryParam("tags"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "tags", ctx.QueryParams(), ¶ms.Tags) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tags: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + if paramValue := ctx.QueryParam("limit"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(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 echo context to params. +func (w *ServerInterfaceWrapper) AddPet(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.AddPet(ctx) + return err +} + +// DeletePet converts echo context to params. +func (w *ServerInterfaceWrapper) DeletePet(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameter("simple", false, "id", ctx.Param("id"), &id) + if err != nil { + return echo.NewHTTPError(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 echo context to params. +func (w *ServerInterfaceWrapper) FindPetById(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id int64 + + err = runtime.BindStyledParameter("simple", false, "id", ctx.Param("id"), &id) + if err != nil { + return echo.NewHTTPError(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 +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +}, si ServerInterface) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET("/pets", wrapper.FindPets) + router.POST("/pets", wrapper.AddPet) + router.DELETE("/pets/:id", wrapper.DeletePet) + router.GET("/pets/:id", wrapper.FindPetById) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", + "X31/3d3v/hEAAP//81bN0nkSAAA=", +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. +func GetSwagger() (*openapi3.Swagger, 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) + } + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("error loading Swagger: %s", err) + } + return swagger, nil +} + diff --git a/examples/oapi-codegen/petstore.go b/examples/oapi-codegen/petstore.go new file mode 100644 index 000000000..be11e4f4a --- /dev/null +++ b/examples/oapi-codegen/petstore.go @@ -0,0 +1,33 @@ +// This is an example of implementing the Pet Store from the OpenAPI documentation +// found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + + api "github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api" +) + +func main() { + var port = flag.Int("port", 8080, "Port for test HTTP server") + flag.Parse() + + // Create an instance of our handler which satisfies the generated interface + petStore := api.NewPetStore() + + // We now register our petStore above as the handler for the interface + h := api.Handler(petStore) + + s := &http.Server{ + Handler: h, + Addr: fmt.Sprintf("0.0.0.0:%d", *port), + } + + // And we serve HTTP until the world ends. + log.Fatal(s.ListenAndServe()) +} diff --git a/examples/oapi-codegen/petstore_test.go b/examples/oapi-codegen/petstore_test.go new file mode 100644 index 000000000..f87b71634 --- /dev/null +++ b/examples/oapi-codegen/petstore_test.go @@ -0,0 +1,156 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api" +) + +func DoJson(handler http.Handler, method string, url string, body interface{}) *httptest.ResponseRecorder { + rr := httptest.NewRecorder() + b, _ := json.Marshal(body) + req, _ := http.NewRequest(method, url, bytes.NewBuffer(b)) + handler.ServeHTTP(rr, req) + + return rr +} + +func TestPetStore(t *testing.T) { + var err error + + store := api.NewPetStore() + h := api.Handler(store) + + t.Run("Add pet", func(t *testing.T) { + tag := "TagOfSpot" + newPet := api.NewPet{ + Name: "Spot", + Tag: &tag, + } + + rr := DoJson(h, "POST", "/pets", newPet) + assert.Equal(t, http.StatusCreated, rr.Code) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newPet.Name, resultPet.Name) + assert.Equal(t, *newPet.Tag, *resultPet.Tag) + }) + + t.Run("Find pet by ID", func(t *testing.T) { + pet := api.Pet{ + Id: 100, + } + + store.Pets[pet.Id] = pet + rr := DoJson(h, "GET", fmt.Sprintf("/pets/%d", pet.Id), nil) + + var resultPet api.Pet + err = json.NewDecoder(rr.Body).Decode(&resultPet) + assert.NoError(t, err, "error getting pet") + assert.Equal(t, pet, resultPet) + }) + + t.Run("Pet not found", func(t *testing.T) { + rr := DoJson(h, "GET", "/pets/27179095781", nil) + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + }) + + t.Run("List all pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Now, list all pets, we should have two + rr := DoJson(h, "GET", "/pets", nil) + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 2, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + tag := "TagOfFido" + + store.Pets = map[int64]api.Pet{ + 1: api.Pet{ + NewPet: api.NewPet{ + Tag: &tag, + }, + }, + 2: api.Pet{}, + } + + // Filter pets by tag, we should have 1 + rr := DoJson(h, "GET", "/pets?tags=TagOfFido", nil) + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 1, len(petList)) + }) + + t.Run("Filter pets by tag", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Filter pets by non existent tag, we should have 0 + rr := DoJson(h, "GET", "/pets?tags=NotExists", nil) + assert.Equal(t, http.StatusOK, rr.Code) + + var petList []api.Pet + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) + + t.Run("Delete pets", func(t *testing.T) { + store.Pets = map[int64]api.Pet{ + 1: api.Pet{}, + 2: api.Pet{}, + } + + // Let's delete non-existent pet + rr := DoJson(h, "DELETE", "/pets/7", nil) + assert.Equal(t, http.StatusNotFound, rr.Code) + + var petError api.Error + err = json.NewDecoder(rr.Body).Decode(&petError) + assert.NoError(t, err, "error unmarshaling PetError") + assert.Equal(t, int32(http.StatusNotFound), petError.Code) + + // Now, delete both real pets + rr = DoJson(h, "DELETE", "/pets/1", nil) + assert.Equal(t, http.StatusNoContent, rr.Code) + + rr = DoJson(h, "DELETE", "/pets/2", nil) + assert.Equal(t, http.StatusNoContent, rr.Code) + + // Should have no pets left. + var petList []api.Pet + rr = DoJson(h, "GET", "/pets", nil) + assert.Equal(t, http.StatusOK, rr.Code) + err = json.NewDecoder(rr.Body).Decode(&petList) + assert.NoError(t, err, "error getting response", err) + assert.Equal(t, 0, len(petList)) + }) +} diff --git a/examples/petstore-expanded/README.md b/examples/petstore-expanded/README.md index 5a7d0c659..01f693d1c 100644 --- a/examples/petstore-expanded/README.md +++ b/examples/petstore-expanded/README.md @@ -1,3 +1,9 @@ +--- +noteId: "f6117f40b9f511eba0c64ffabff3407d" +tags: [] + +--- + OpenAPI Code Generation Example -------------------------------