diff --git a/README.md b/README.md index ba45d6b92c..295edf35a1 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ type ServerInterface interface { These are the functions which you will implement yourself in order to create a server conforming to the API specification. Normally, all the arguments and parameters are stored on the `echo.Context` in handlers, so we do the tedious -work of of unmarshaling the JSON automatically, simply passing values into +work of unmarshalling the JSON automatically, simply passing values into your handlers. Notice that `FindPetById` takes a parameter `id int64`. All path arguments diff --git a/examples/custom-client-type/api.yaml b/examples/custom-client-type/api.yaml new file mode 100644 index 0000000000..838d862072 --- /dev/null +++ b/examples/custom-client-type/api.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Custom Client Type Example +paths: + /client: + get: + operationId: getClient + responses: + 200: + content: + application/json: + schema: + $ref: "#/components/schemas/Client" +components: + schemas: + Client: + type: object + required: + - name + properties: + name: + type: string diff --git a/examples/custom-client-type/cfg.yaml b/examples/custom-client-type/cfg.yaml new file mode 100644 index 0000000000..6f52fb3db7 --- /dev/null +++ b/examples/custom-client-type/cfg.yaml @@ -0,0 +1,7 @@ +package: customclienttype +output: custom-client-type.gen.go +generate: + models: true + client: true +output-options: + client-type-name: CustomClientType diff --git a/examples/custom-client-type/custom-client-type.gen.go b/examples/custom-client-type/custom-client-type.gen.go new file mode 100644 index 0000000000..34c34ea99e --- /dev/null +++ b/examples/custom-client-type/custom-client-type.gen.go @@ -0,0 +1,239 @@ +// Package customclienttype provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package customclienttype + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// Client defines model for Client. +type Client struct { + Name string `json:"name"` +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// CustomClientType which conforms to the OpenAPI3 specification for this service. +type CustomClientType struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*CustomClientType) error + +// Creates a new CustomClientType, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*CustomClientType, error) { + // create a client with sane default values + client := CustomClientType{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *CustomClientType) 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 *CustomClientType) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetClient request + GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *CustomClientType) GetClient(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetClientRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetClientRequest generates requests for GetClient +func NewGetClientRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/client") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *CustomClientType) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *CustomClientType) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetClient request + GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) +} + +type GetClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Client +} + +// Status returns HTTPResponse.Status +func (r GetClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetClientWithResponse request returning *GetClientResponse +func (c *ClientWithResponses) GetClientWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClientResponse, error) { + rsp, err := c.GetClient(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetClientResponse(rsp) +} + +// ParseGetClientResponse parses an HTTP response from a GetClientWithResponse call +func ParseGetClientResponse(rsp *http.Response) (*GetClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Client + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/examples/custom-client-type/doc.go b/examples/custom-client-type/doc.go new file mode 100644 index 0000000000..b718ac2cb3 --- /dev/null +++ b/examples/custom-client-type/doc.go @@ -0,0 +1,6 @@ +package customclienttype + +// This is an example of how to add a prefix to the name of the generated Client struct +// See https://github.com/deepmap/oapi-codegen/issues/785 for why this might be necessary + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config cfg.yaml api.yaml diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go index 59e517c8cc..66505c564f 100644 --- a/examples/petstore-expanded/chi/api/petstore.gen.go +++ b/examples/petstore-expanded/chi/api/petstore.gen.go @@ -201,16 +201,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/examples/petstore-expanded/chi/api/petstore.go b/examples/petstore-expanded/chi/api/petstore.go index 2b3dc97331..838c961165 100644 --- a/examples/petstore-expanded/chi/api/petstore.go +++ b/examples/petstore-expanded/chi/api/petstore.go @@ -28,7 +28,7 @@ func NewPetStore() *PetStore { // This function wraps sending of an error in the Error format, and // handling the failure to marshal that. -func sendPetstoreError(w http.ResponseWriter, code int, message string) { +func sendPetStoreError(w http.ResponseWriter, code int, message string) { petErr := Error{ Code: int32(code), Message: message, @@ -74,7 +74,7 @@ func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { // We expect a NewPet object in the request body. var newPet NewPet if err := json.NewDecoder(r.Body).Decode(&newPet); err != nil { - sendPetstoreError(w, http.StatusBadRequest, "Invalid format for NewPet") + sendPetStoreError(w, http.StatusBadRequest, "Invalid format for NewPet") return } @@ -105,7 +105,7 @@ func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) pet, found := p.Pets[id] if !found { - sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) return } @@ -119,7 +119,7 @@ func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { _, found := p.Pets[id] if !found { - sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) return } delete(p.Pets, id) diff --git a/examples/petstore-expanded/chi/petstore_test.go b/examples/petstore-expanded/chi/petstore_test.go index feedfb55d3..687c4d14f2 100644 --- a/examples/petstore-expanded/chi/petstore_test.go +++ b/examples/petstore-expanded/chi/petstore_test.go @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshaling PetError") + assert.NoError(t, err, "error unmarshalling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/echo/api/petstore.go b/examples/petstore-expanded/echo/api/petstore.go index 70b696cf42..65229d0bbe 100644 --- a/examples/petstore-expanded/echo/api/petstore.go +++ b/examples/petstore-expanded/echo/api/petstore.go @@ -41,7 +41,7 @@ func NewPetStore() *PetStore { // This function wraps sending of an error in the Error format, and // handling the failure to marshal that. -func sendPetstoreError(ctx echo.Context, code int, message string) error { +func sendPetStoreError(ctx echo.Context, code int, message string) error { petErr := models.Error{ Code: int32(code), Message: message, @@ -86,7 +86,7 @@ func (p *PetStore) AddPet(ctx echo.Context) error { var newPet models.NewPet err := ctx.Bind(&newPet) if err != nil { - return sendPetstoreError(ctx, http.StatusBadRequest, "Invalid format for NewPet") + return sendPetStoreError(ctx, http.StatusBadRequest, "Invalid format for NewPet") } // We now have a pet, let's add it to our "database". @@ -126,7 +126,7 @@ func (p *PetStore) FindPetByID(ctx echo.Context, petId int64) error { pet, found := p.Pets[petId] if !found { - return sendPetstoreError(ctx, http.StatusNotFound, + return sendPetStoreError(ctx, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", petId)) } return ctx.JSON(http.StatusOK, pet) @@ -138,7 +138,7 @@ func (p *PetStore) DeletePet(ctx echo.Context, id int64) error { _, found := p.Pets[id] if !found { - return sendPetstoreError(ctx, http.StatusNotFound, + return sendPetStoreError(ctx, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) } delete(p.Pets, id) diff --git a/examples/petstore-expanded/echo/petstore_test.go b/examples/petstore-expanded/echo/petstore_test.go index 8570cded33..c99a45d4e5 100644 --- a/examples/petstore-expanded/echo/petstore_test.go +++ b/examples/petstore-expanded/echo/petstore_test.go @@ -72,7 +72,7 @@ func TestPetStore(t *testing.T) { // sure that its fields match. var resultPet models.Pet err = result.UnmarshalBodyToObject(&resultPet) - assert.NoError(t, err, "error unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) @@ -106,7 +106,7 @@ func TestPetStore(t *testing.T) { // 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 unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") petId2 := resultPet.Id // Now, list all pets, we should have two @@ -137,7 +137,7 @@ func TestPetStore(t *testing.T) { result = testutil.NewRequest().Delete("/pets/7").Go(t, e) assert.Equal(t, http.StatusNotFound, result.Code()) err = result.UnmarshalBodyToObject(&petError) - assert.NoError(t, err, "error unmarshaling PetError") + assert.NoError(t, err, "error unmarshalling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/gin/api/petstore-server.gen.go b/examples/petstore-expanded/gin/api/petstore-server.gen.go index b05b8437a0..8dabdd9e76 100644 --- a/examples/petstore-expanded/gin/api/petstore-server.gen.go +++ b/examples/petstore-expanded/gin/api/petstore-server.gen.go @@ -38,6 +38,7 @@ type ServerInterface interface { type ServerInterfaceWrapper struct { Handler ServerInterface HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } type MiddlewareFunc func(c *gin.Context) @@ -54,7 +55,7 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) { err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter tags: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) return } @@ -62,7 +63,7 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) { err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter limit: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %s", err), http.StatusBadRequest) return } @@ -93,7 +94,7 @@ func (siw *ServerInterfaceWrapper) DeletePet(c *gin.Context) { err = runtime.BindStyledParameter("simple", false, "id", c.Param("id"), &id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter id: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %s", err), http.StatusBadRequest) return } @@ -114,7 +115,7 @@ func (siw *ServerInterfaceWrapper) FindPetByID(c *gin.Context) { err = runtime.BindStyledParameter("simple", false, "id", c.Param("id"), &id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter id: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter id: %s", err), http.StatusBadRequest) return } @@ -127,8 +128,9 @@ func (siw *ServerInterfaceWrapper) FindPetByID(c *gin.Context) { // GinServerOptions provides options for the Gin server. type GinServerOptions struct { - BaseURL string - Middlewares []MiddlewareFunc + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } // RegisterHandlers creates http.Handler with routing matching OpenAPI spec. @@ -138,9 +140,19 @@ func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine { // RegisterHandlersWithOptions creates http.Handler with additional options func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { + + errorHandler := options.ErrorHandler + + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + wrapper := ServerInterfaceWrapper{ Handler: si, HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, } router.GET(options.BaseURL+"/pets", wrapper.FindPets) diff --git a/examples/petstore-expanded/gin/api/petstore.go b/examples/petstore-expanded/gin/api/petstore.go index f79d282bae..2654d37986 100644 --- a/examples/petstore-expanded/gin/api/petstore.go +++ b/examples/petstore-expanded/gin/api/petstore.go @@ -40,7 +40,7 @@ func NewPetStore() *PetStore { // This function wraps sending of an error in the Error format, and // handling the failure to marshal that. -func sendPetstoreError(c *gin.Context, code int, message string) { +func sendPetStoreError(c *gin.Context, code int, message string) { petErr := Error{ Code: int32(code), Message: message, @@ -84,7 +84,7 @@ func (p *PetStore) AddPet(c *gin.Context) { var newPet NewPet err := c.Bind(&newPet) if err != nil { - sendPetstoreError(c, http.StatusBadRequest, "Invalid format for NewPet") + sendPetStoreError(c, http.StatusBadRequest, "Invalid format for NewPet") return } // We now have a pet, let's add it to our "database". @@ -114,7 +114,7 @@ func (p *PetStore) FindPetByID(c *gin.Context, petId int64) { pet, found := p.Pets[petId] if !found { - sendPetstoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", petId)) + sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", petId)) return } c.JSON(http.StatusOK, pet) @@ -126,7 +126,7 @@ func (p *PetStore) DeletePet(c *gin.Context, id int64) { _, found := p.Pets[id] if !found { - sendPetstoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + sendPetStoreError(c, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) } delete(p.Pets, id) c.Status(http.StatusNoContent) diff --git a/examples/petstore-expanded/gin/petstore_test.go b/examples/petstore-expanded/gin/petstore_test.go index 7975f148b7..814135ba90 100644 --- a/examples/petstore-expanded/gin/petstore_test.go +++ b/examples/petstore-expanded/gin/petstore_test.go @@ -35,7 +35,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -119,7 +119,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshaling PetError") + assert.NoError(t, err, "error unmarshalling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/gorilla/api/petstore.gen.go b/examples/petstore-expanded/gorilla/api/petstore.gen.go index b56e03ce5b..deac9824ad 100644 --- a/examples/petstore-expanded/gorilla/api/petstore.gen.go +++ b/examples/petstore-expanded/gorilla/api/petstore.gen.go @@ -201,16 +201,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/examples/petstore-expanded/gorilla/api/petstore.go b/examples/petstore-expanded/gorilla/api/petstore.go index 2b3dc97331..838c961165 100644 --- a/examples/petstore-expanded/gorilla/api/petstore.go +++ b/examples/petstore-expanded/gorilla/api/petstore.go @@ -28,7 +28,7 @@ func NewPetStore() *PetStore { // This function wraps sending of an error in the Error format, and // handling the failure to marshal that. -func sendPetstoreError(w http.ResponseWriter, code int, message string) { +func sendPetStoreError(w http.ResponseWriter, code int, message string) { petErr := Error{ Code: int32(code), Message: message, @@ -74,7 +74,7 @@ func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) { // We expect a NewPet object in the request body. var newPet NewPet if err := json.NewDecoder(r.Body).Decode(&newPet); err != nil { - sendPetstoreError(w, http.StatusBadRequest, "Invalid format for NewPet") + sendPetStoreError(w, http.StatusBadRequest, "Invalid format for NewPet") return } @@ -105,7 +105,7 @@ func (p *PetStore) FindPetByID(w http.ResponseWriter, r *http.Request, id int64) pet, found := p.Pets[id] if !found { - sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) return } @@ -119,7 +119,7 @@ func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request, id int64) { _, found := p.Pets[id] if !found { - sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) + sendPetStoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id)) return } delete(p.Pets, id) diff --git a/examples/petstore-expanded/gorilla/petstore_test.go b/examples/petstore-expanded/gorilla/petstore_test.go index 0a16243905..6ba950489c 100644 --- a/examples/petstore-expanded/gorilla/petstore_test.go +++ b/examples/petstore-expanded/gorilla/petstore_test.go @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshaling PetError") + assert.NoError(t, err, "error unmarshalling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/examples/petstore-expanded/strict/api/petstore-server.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go index 8901ebb46f..cd9352ac11 100644 --- a/examples/petstore-expanded/strict/api/petstore-server.gen.go +++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go @@ -161,16 +161,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/examples/petstore-expanded/strict/petstore_test.go b/examples/petstore-expanded/strict/petstore_test.go index 6b3888fd59..68a77358e3 100644 --- a/examples/petstore-expanded/strict/petstore_test.go +++ b/examples/petstore-expanded/strict/petstore_test.go @@ -53,7 +53,7 @@ func TestPetStore(t *testing.T) { var resultPet api.Pet err = json.NewDecoder(rr.Body).Decode(&resultPet) - assert.NoError(t, err, "error unmarshaling response") + assert.NoError(t, err, "error unmarshalling response") assert.Equal(t, newPet.Name, resultPet.Name) assert.Equal(t, *newPet.Tag, *resultPet.Tag) }) @@ -146,7 +146,7 @@ func TestPetStore(t *testing.T) { var petError api.Error err = json.NewDecoder(rr.Body).Decode(&petError) - assert.NoError(t, err, "error unmarshaling PetError") + assert.NoError(t, err, "error unmarshalling PetError") assert.Equal(t, int32(http.StatusNotFound), petError.Code) // Now, delete both real pets diff --git a/go.mod b/go.mod index 809f0d6252..3e222b7e58 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/deepmap/oapi-codegen require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 - github.com/getkin/kin-openapi v0.106.0 + github.com/getkin/kin-openapi v0.107.0 github.com/gin-gonic/gin v1.8.1 github.com/go-chi/chi/v5 v5.0.7 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 @@ -25,8 +25,8 @@ require ( github.com/go-openapi/swag v0.21.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.0 // indirect - github.com/goccy/go-json v0.9.7 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -39,25 +39,25 @@ require ( github.com/lestrrat-go/iter v1.0.1 // indirect github.com/lestrrat-go/option v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // 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.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/mod v0.6.0 // indirect golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index dc49b3fb3a..c8c1e6badc 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/getkin/kin-openapi v0.106.0 h1:hrqfqJPAvWvuO/V0lCr/xyQOq4Gy21mcr28JJOSRcEI= -github.com/getkin/kin-openapi v0.106.0/go.mod h1:9Dhr+FasATJZjS4iOLvB0hkaxgYdulrNYm2e9epLWOo= +github.com/getkin/kin-openapi v0.107.0 h1:bxhL6QArW7BXQj8NjXfIJQy680NsMKd25nwhvpCXchg= +github.com/getkin/kin-openapi v0.107.0/go.mod h1:9Dhr+FasATJZjS4iOLvB0hkaxgYdulrNYm2e9epLWOo= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= @@ -29,10 +29,11 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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= @@ -86,10 +87,11 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matryer/moq v0.2.7 h1:RtpiPUM8L7ZSCbSwK+QcZH/E9tgqAkFjKQxsRs25b4w= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= @@ -98,8 +100,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -126,8 +128,9 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 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/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 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/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -155,6 +158,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -178,8 +182,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T 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= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/test/any_of/param/config.yaml b/internal/test/any_of/param/config.yaml new file mode 100644 index 0000000000..7b2827042a --- /dev/null +++ b/internal/test/any_of/param/config.yaml @@ -0,0 +1,5 @@ +package: param +generate: + models: true + client: true +output: param.gen.go diff --git a/internal/test/any_of/param/doc.go b/internal/test/any_of/param/doc.go new file mode 100644 index 0000000000..63ff22389a --- /dev/null +++ b/internal/test/any_of/param/doc.go @@ -0,0 +1,3 @@ +package param + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/any_of/param/param.gen.go b/internal/test/any_of/param/param.gen.go new file mode 100644 index 0000000000..7de9459481 --- /dev/null +++ b/internal/test/any_of/param/param.gen.go @@ -0,0 +1,419 @@ +// Package param provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package param + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" +) + +// Test defines model for test. +type Test struct { + union json.RawMessage +} + +// Test0 defines model for . +type Test0 struct { + Item1 string `json:"item1"` + Item2 string `json:"item2"` +} + +// Test1 defines model for . +type Test1 struct { + Item2 *string `json:"item2,omitempty"` + Item3 *string `json:"item3,omitempty"` +} + +// Test2 defines model for test2. +type Test2 struct { + union json.RawMessage +} + +// Test20 defines model for . +type Test20 = int + +// Test21 defines model for . +type Test21 = string + +// GetTestParams defines parameters for GetTest. +type GetTestParams struct { + Test *Test `form:"test,omitempty" json:"test,omitempty"` + Test2 *[]Test2 `form:"test2,omitempty" json:"test2,omitempty"` +} + +// AsTest0 returns the union data inside the Test as a Test0 +func (t Test) AsTest0() (Test0, error) { + var body Test0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTest0 overwrites any union data inside the Test as the provided Test0 +func (t *Test) FromTest0(v Test0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTest0 performs a merge with any union data inside the Test, using the provided Test0 +func (t *Test) MergeTest0(v Test0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(b, t.union) + t.union = merged + return err +} + +// AsTest1 returns the union data inside the Test as a Test1 +func (t Test) AsTest1() (Test1, error) { + var body Test1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTest1 overwrites any union data inside the Test as the provided Test1 +func (t *Test) FromTest1(v Test1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTest1 performs a merge with any union data inside the Test, using the provided Test1 +func (t *Test) MergeTest1(v Test1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(b, t.union) + t.union = merged + return err +} + +func (t Test) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *Test) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsTest20 returns the union data inside the Test2 as a Test20 +func (t Test2) AsTest20() (Test20, error) { + var body Test20 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTest20 overwrites any union data inside the Test2 as the provided Test20 +func (t *Test2) FromTest20(v Test20) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTest20 performs a merge with any union data inside the Test2, using the provided Test20 +func (t *Test2) MergeTest20(v Test20) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(b, t.union) + t.union = merged + return err +} + +// AsTest21 returns the union data inside the Test2 as a Test21 +func (t Test2) AsTest21() (Test21, error) { + var body Test21 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTest21 overwrites any union data inside the Test2 as the provided Test21 +func (t *Test2) FromTest21(v Test21) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTest21 performs a merge with any union data inside the Test2, using the provided Test21 +func (t *Test2) MergeTest21(v Test21) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(b, t.union) + t.union = merged + return err +} + +func (t Test2) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *Test2) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetTest request + GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetTest(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTestRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetTestRequest generates requests for GetTest +func NewGetTestRequest(server string, params *GetTestParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/test") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + queryValues := queryURL.Query() + + if params.Test != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "test", runtime.ParamLocationQuery, *params.Test); 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.Test2 != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "test2", runtime.ParamLocationQuery, *params.Test2); 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 +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetTest request + GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) +} + +type GetTestResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetTestResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTestResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetTestWithResponse request returning *GetTestResponse +func (c *ClientWithResponses) GetTestWithResponse(ctx context.Context, params *GetTestParams, reqEditors ...RequestEditorFn) (*GetTestResponse, error) { + rsp, err := c.GetTest(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTestResponse(rsp) +} + +// ParseGetTestResponse parses an HTTP response from a GetTestWithResponse call +func ParseGetTestResponse(rsp *http.Response) (*GetTestResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTestResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} diff --git a/internal/test/any_of/param/param_test.go b/internal/test/any_of/param/param_test.go new file mode 100644 index 0000000000..748ebc54e2 --- /dev/null +++ b/internal/test/any_of/param/param_test.go @@ -0,0 +1,38 @@ +package param_test + +import ( + "testing" + + "github.com/deepmap/oapi-codegen/internal/test/any_of/param" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAnyOfParameter(t *testing.T) { + var p param.GetTestParams + + p.Test = new(param.Test) + err := p.Test.FromTest0(param.Test0{ + Item1: "foo", + Item2: "bar", + }) + require.NoError(t, err) + + hp, err := param.NewGetTestRequest("", &p) + assert.NoError(t, err) + assert.Equal(t, "/test?item1=foo&item2=bar", hp.URL.String()) +} + +func TestArrayOfAnyOfParameter(t *testing.T) { + var p param.GetTestParams + + p.Test2 = &[]param.Test2{ + {}, + } + err := (*p.Test2)[0].FromTest20(100) + require.NoError(t, err) + + hp, err := param.NewGetTestRequest("", &p) + assert.NoError(t, err) + assert.Equal(t, "/test?test2=100", hp.URL.String()) +} diff --git a/internal/test/any_of/param/spec.yaml b/internal/test/any_of/param/spec.yaml new file mode 100644 index 0000000000..090c97c6c1 --- /dev/null +++ b/internal/test/any_of/param/spec.yaml @@ -0,0 +1,42 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: AnyOf parameter + description: Checks parameter serialize has AnyOf +paths: + /test: + get: + parameters: + - name: test + in: query + schema: + $ref: '#/components/schemas/test' + - name: test2 + in: query + schema: + type: array + items: + $ref: '#/components/schemas/test2' +components: + schemas: + test: + anyOf: + - type: object + required: + - item1 + - item2 + properties: + item1: + type: string + item2: + type: string + - type: object + properties: + item2: + type: string + item3: + type: string + test2: + oneOf: + - type: integer + - type: string diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index 255347138d..d7e23d31c9 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -243,7 +243,7 @@ type OneOfObject3_Union struct { union json.RawMessage } -// OneOfObject4 oneOf plus fixed type - custom marshaling/unmarshaling +// OneOfObject4 oneOf plus fixed type - custom marshaling/unmarshalling type OneOfObject4 struct { FixedProperty *string `json:"fixedProperty,omitempty"` union json.RawMessage @@ -435,7 +435,7 @@ func (a *BodyWithAddPropsJSONBody) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -522,7 +522,7 @@ func (a *AdditionalPropertiesObject1) UnmarshalJSON(b []byte) error { var fieldVal int err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -600,7 +600,7 @@ func (a *AdditionalPropertiesObject3) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -674,7 +674,7 @@ func (a *AdditionalPropertiesObject4) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -745,7 +745,7 @@ func (a *AdditionalPropertiesObject4_Inner) UnmarshalJSON(b []byte) error { var fieldVal interface{} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } @@ -987,19 +987,25 @@ func (t OneOfObject10) MarshalJSON() ([]byte, error) { } } - object["one"], err = json.Marshal(t.One) - if err != nil { - return nil, fmt.Errorf("error marshaling 'one': %w", err) + if t.One != nil { + object["one"], err = json.Marshal(t.One) + if err != nil { + return nil, fmt.Errorf("error marshaling 'one': %w", err) + } } - object["three"], err = json.Marshal(t.Three) - if err != nil { - return nil, fmt.Errorf("error marshaling 'three': %w", err) + if t.Three != nil { + object["three"], err = json.Marshal(t.Three) + if err != nil { + return nil, fmt.Errorf("error marshaling 'three': %w", err) + } } - object["two"], err = json.Marshal(t.Two) - if err != nil { - return nil, fmt.Errorf("error marshaling 'two': %w", err) + if t.Two != nil { + object["two"], err = json.Marshal(t.Two) + if err != nil { + return nil, fmt.Errorf("error marshaling 'two': %w", err) + } } b, err = json.Marshal(object) return b, err @@ -1509,9 +1515,11 @@ func (t OneOfObject4) MarshalJSON() ([]byte, error) { } } - object["fixedProperty"], err = json.Marshal(t.FixedProperty) - if err != nil { - return nil, fmt.Errorf("error marshaling 'fixedProperty': %w", err) + if t.FixedProperty != nil { + object["fixedProperty"], err = json.Marshal(t.FixedProperty) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fixedProperty': %w", err) + } } b, err = json.Marshal(object) return b, err @@ -1824,9 +1832,11 @@ func (t OneOfObject8) MarshalJSON() ([]byte, error) { } } - object["fixed"], err = json.Marshal(t.Fixed) - if err != nil { - return nil, fmt.Errorf("error marshaling 'fixed': %w", err) + if t.Fixed != nil { + object["fixed"], err = json.Marshal(t.Fixed) + if err != nil { + return nil, fmt.Errorf("error marshaling 'fixed': %w", err) + } } b, err = json.Marshal(object) return b, err @@ -1953,6 +1963,7 @@ func (t OneOfObject9) MarshalJSON() ([]byte, error) { if err != nil { return nil, fmt.Errorf("error marshaling 'type': %w", err) } + b, err = json.Marshal(object) return b, err } diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml index 4bc4fe797c..79e3857361 100644 --- a/internal/test/components/components.yaml +++ b/internal/test/components/components.yaml @@ -234,7 +234,7 @@ components: - $ref: '#/components/schemas/OneOfVariant2' - $ref: '#/components/schemas/OneOfVariant3' OneOfObject4: - description: oneOf plus fixed type - custom marshaling/unmarshaling + description: oneOf plus fixed type - custom marshaling/unmarshalling type: object properties: fixedProperty: diff --git a/internal/test/components/components_test.go b/internal/test/components/components_test.go index 4e8f9c4080..e70ea35124 100644 --- a/internal/test/components/components_test.go +++ b/internal/test/components/components_test.go @@ -20,7 +20,7 @@ func assertJSONEqual(t *testing.T, j1 []byte, j2 []byte) { } func TestRawJSON(t *testing.T) { - // Check raw json unmarshaling + // Check raw json unmarshalling const buf = `{"name":"bob","value1":{"present":true}}` var dst ObjectWithJsonField err := json.Unmarshal([]byte(buf), &dst) @@ -204,7 +204,7 @@ func TestAnyOf(t *testing.T) { } func TestMarshalWhenNoUnionValueSet(t *testing.T) { - const expected = `{"one":null,"three":null,"two":null}` + const expected = `{}` var dst OneOfObject10 diff --git a/internal/test/issues/issue-head-digit-of-operation-id/config.yaml b/internal/test/issues/issue-head-digit-of-operation-id/config.yaml new file mode 100644 index 0000000000..5823c79ebb --- /dev/null +++ b/internal/test/issues/issue-head-digit-of-operation-id/config.yaml @@ -0,0 +1,7 @@ +--- +package: head_digit_of_operation_id +generate: + strict-server: true +output: issue.gen.go +output-options: + skip-prune: true diff --git a/internal/test/issues/issue-head-digit-of-operation-id/doc.go b/internal/test/issues/issue-head-digit-of-operation-id/doc.go new file mode 100644 index 0000000000..6faae82c83 --- /dev/null +++ b/internal/test/issues/issue-head-digit-of-operation-id/doc.go @@ -0,0 +1,3 @@ +package head_digit_of_operation_id + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go new file mode 100644 index 0000000000..17607466a4 --- /dev/null +++ b/internal/test/issues/issue-head-digit-of-operation-id/issue.gen.go @@ -0,0 +1,23 @@ +// Package head_digit_of_operation_id provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package head_digit_of_operation_id + +import ( + "context" + "net/http" +) + +type N3GPPFooRequestObject struct { +} + +type N3GPPFooResponseObject interface { + VisitN3GPPFooResponse(w http.ResponseWriter) error +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (GET /3gpp/foo) + N3GPPFoo(ctx context.Context, request N3GPPFooRequestObject) (N3GPPFooResponseObject, error) +} diff --git a/internal/test/issues/issue-head-digit-of-operation-id/spec.yaml b/internal/test/issues/issue-head-digit-of-operation-id/spec.yaml new file mode 100644 index 0000000000..59efb77502 --- /dev/null +++ b/internal/test/issues/issue-head-digit-of-operation-id/spec.yaml @@ -0,0 +1,7 @@ +openapi: 3.0.2 +info: + version: "0.0.1" +paths: + /3gpp/foo: + get: + operationId: 3GPPFoo diff --git a/internal/test/issues/issue-removed-external-ref/config.base.yaml b/internal/test/issues/issue-removed-external-ref/config.base.yaml new file mode 100644 index 0000000000..17d4d2f085 --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/config.base.yaml @@ -0,0 +1,12 @@ +--- +package: spec_base +generate: + chi-server: true + strict-server: true + models: true +import-mapping: + spec-ext.yaml: "github.com/deepmap/oapi-codegen/internal/test/issues/issue-removed-external-ref/gen/spec_ext" +output: gen/spec_base/issue.gen.go +output-options: + skip-prune: true + # skip-fmt: true diff --git a/internal/test/issues/issue-removed-external-ref/config.ext.yaml b/internal/test/issues/issue-removed-external-ref/config.ext.yaml new file mode 100644 index 0000000000..9d0478065a --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/config.ext.yaml @@ -0,0 +1,10 @@ +--- +package: spec_ext +generate: + chi-server: true + strict-server: true + models: true +output: gen/spec_ext/issue.gen.go +output-options: + skip-prune: true + # skip-fmt: true diff --git a/internal/test/issues/issue-removed-external-ref/doc.go b/internal/test/issues/issue-removed-external-ref/doc.go new file mode 100644 index 0000000000..7cb5ab0d8b --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/doc.go @@ -0,0 +1,4 @@ +package head_digit_of_httpheader + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.ext.yaml spec-ext.yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.base.yaml spec-base.yaml diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_base/.gitempty b/internal/test/issues/issue-removed-external-ref/gen/spec_base/.gitempty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go b/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go new file mode 100644 index 0000000000..09e8df32f5 --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/gen/spec_base/issue.gen.go @@ -0,0 +1,323 @@ +// Package spec_base provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package spec_base + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + externalRef0 "github.com/deepmap/oapi-codegen/internal/test/issues/issue-removed-external-ref/gen/spec_ext" + "github.com/go-chi/chi/v5" +) + +// DirectBar defines model for DirectBar. +type DirectBar = externalRef0.Foo + +// PackedBar defines model for PackedBar. +type PackedBar struct { + Core *externalRef0.Foo `json:"core,omitempty"` + Directd *DirectBar `json:"directd,omitempty"` + Id *string `json:"id,omitempty"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + + // (POST /invalidExtRefTrouble) + PostInvalidExtRefTrouble(w http.ResponseWriter, r *http.Request) + + // (POST /noTrouble) + PostNoTrouble(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// PostInvalidExtRefTrouble operation middleware +func (siw *ServerInterfaceWrapper) PostInvalidExtRefTrouble(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PostInvalidExtRefTrouble(w, r) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PostNoTrouble operation middleware +func (siw *ServerInterfaceWrapper) PostNoTrouble(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PostNoTrouble(w, r) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/invalidExtRefTrouble", wrapper.PostInvalidExtRefTrouble) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/noTrouble", wrapper.PostNoTrouble) + }) + + return r +} + +type PostInvalidExtRefTroubleRequestObject struct { +} + +type PostInvalidExtRefTroubleResponseObject interface { + VisitPostInvalidExtRefTroubleResponse(w http.ResponseWriter) error +} + +type PostInvalidExtRefTrouble300JSONResponse struct { + externalRef0.PascalJSONResponse +} + +func (response PostInvalidExtRefTrouble300JSONResponse) VisitPostInvalidExtRefTroubleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(300) + + return json.NewEncoder(w).Encode(response) +} + +type PostNoTroubleRequestObject struct { +} + +type PostNoTroubleResponseObject interface { + VisitPostNoTroubleResponse(w http.ResponseWriter) error +} + +type PostNoTrouble200JSONResponse struct { + DirectBar *DirectBar `json:"directBar,omitempty"` + DirectFoo *externalRef0.Foo `json:"directFoo,omitempty"` + IndirectFoo *PackedBar `json:"indirectFoo,omitempty"` + Name *string `json:"name,omitempty"` +} + +func (response PostNoTrouble200JSONResponse) VisitPostNoTroubleResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + + // (POST /invalidExtRefTrouble) + PostInvalidExtRefTrouble(ctx context.Context, request PostInvalidExtRefTroubleRequestObject) (PostInvalidExtRefTroubleResponseObject, error) + + // (POST /noTrouble) + PostNoTrouble(ctx context.Context, request PostNoTroubleRequestObject) (PostNoTroubleResponseObject, error) +} + +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// PostInvalidExtRefTrouble operation middleware +func (sh *strictHandler) PostInvalidExtRefTrouble(w http.ResponseWriter, r *http.Request) { + var request PostInvalidExtRefTroubleRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PostInvalidExtRefTrouble(ctx, request.(PostInvalidExtRefTroubleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PostInvalidExtRefTrouble") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PostInvalidExtRefTroubleResponseObject); ok { + if err := validResponse.VisitPostInvalidExtRefTroubleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// PostNoTrouble operation middleware +func (sh *strictHandler) PostNoTrouble(w http.ResponseWriter, r *http.Request) { + var request PostNoTroubleRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PostNoTrouble(ctx, request.(PostNoTroubleRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PostNoTrouble") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PostNoTroubleResponseObject); ok { + if err := validResponse.VisitPostNoTroubleResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_ext/.gitempty b/internal/test/issues/issue-removed-external-ref/gen/spec_ext/.gitempty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go b/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go new file mode 100644 index 0000000000..21222f879f --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/gen/spec_ext/issue.gen.go @@ -0,0 +1,192 @@ +// Package spec_ext provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package spec_ext + +import ( + "context" + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +// CamelSchema defines model for CamelSchema. +type CamelSchema struct { + Id *string `json:"id,omitempty"` +} + +// Foo defines model for Foo. +type Foo struct { + CamelSchema *CamelSchema `json:"CamelSchema,omitempty"` + InternalAttr *string `json:"internalAttr,omitempty"` + PascalSchema *PascalSchema `json:"pascalSchema,omitempty"` +} + +// PascalSchema defines model for pascalSchema. +type PascalSchema struct { + Id *string `json:"id,omitempty"` +} + +// Pascal defines model for pascal. +type Pascal = PascalSchema + +// ServerInterface represents all server handlers. +type ServerInterface interface { +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + + return r +} + +type PascalJSONResponse PascalSchema + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { +} + +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} diff --git a/internal/test/issues/issue-removed-external-ref/spec-base.yaml b/internal/test/issues/issue-removed-external-ref/spec-base.yaml new file mode 100644 index 0000000000..36f285cc30 --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/spec-base.yaml @@ -0,0 +1,41 @@ +eopenapi: 3.0.2 +info: + version: "0.0.1" +paths: + /noTrouble: + post: + responses: + 200: + description: ... + content: + application/json: + schema: + type: object + properties: + directFoo: + $ref: "spec-ext.yaml#/components/schemas/Foo" + directBar: + $ref: "#/components/schemas/DirectBar" + indirectFoo: + $ref: "#/components/schemas/PackedBar" + name: + type: string + /invalidExtRefTrouble: + post: + responses: + 300: + $ref: "spec-ext.yaml#/components/responses/pascal" + +components: + schemas: + DirectBar: + $ref: "spec-ext.yaml#/components/schemas/Foo" + PackedBar: + type: object + properties: + id: + type: string + core: + $ref: "spec-ext.yaml#/components/schemas/Foo" + directd: + $ref: "#/components/schemas/DirectBar" diff --git a/internal/test/issues/issue-removed-external-ref/spec-ext.yaml b/internal/test/issues/issue-removed-external-ref/spec-ext.yaml new file mode 100644 index 0000000000..4fd3b4ed34 --- /dev/null +++ b/internal/test/issues/issue-removed-external-ref/spec-ext.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.2 +info: + version: "0.0.1" +components: + schemas: + CamelSchema: + type: object + properties: + id: + type: string + pascalSchema: + type: object + properties: + id: + type: string + Foo: + type: object + properties: + internalAttr: + type: string + CamelSchema: + $ref: "#/components/schemas/CamelSchema" + pascalSchema: + $ref: "#/components/schemas/pascalSchema" + responses: + "pascal": + content: + application/json: + schema: + $ref: "#/components/schemas/pascalSchema" diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go index 9d0fc0e49e..45d11b462c 100644 --- a/internal/test/parameters/parameters.gen.go +++ b/internal/test/parameters/parameters.gen.go @@ -2746,7 +2746,7 @@ func (w *ServerInterfaceWrapper) GetContentObject(ctx echo.Context) error { err = json.Unmarshal([]byte(ctx.Param("param")), ¶m) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'param' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'param' as JSON") } // Invoke the callback with all the unmarshalled arguments @@ -2837,7 +2837,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error { } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'co' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'co' as JSON") } params.Co = &value @@ -2985,7 +2985,7 @@ func (w *ServerInterfaceWrapper) GetHeader(ctx echo.Context) error { err = json.Unmarshal([]byte(valueList[0]), &XComplexObject) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'X-Complex-Object' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'X-Complex-Object' as JSON") } params.XComplexObject = &XComplexObject @@ -3232,7 +3232,7 @@ func (w *ServerInterfaceWrapper) GetQueryForm(ctx echo.Context) error { var value ComplexObject err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter 'co' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter 'co' as JSON") } params.Co = &value diff --git a/internal/test/server/doc.go b/internal/test/server/doc.go index 91e93b59de..1f7a617f9d 100644 --- a/internal/test/server/doc.go +++ b/internal/test/server/doc.go @@ -1,4 +1,8 @@ package server //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml ../test-schema.yaml -//go:generate go run github.com/matryer/moq -out server_moq.gen.go . ServerInterface + +// This is commented out because the server_mog.gen.go keeps changing for no good reason, and +// so, precommit checks fail. We need to regenerate this file occasionally manually. +// TODO(mromaszewicz) - figure out why this file drifts and fix it. +// go:generate go run github.com/matryer/moq -out server_moq.gen.go . ServerInterface diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go index d676d16256..d43de70d1a 100644 --- a/internal/test/server/server.gen.go +++ b/internal/test/server/server.gen.go @@ -458,16 +458,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/internal/test/server/server_moq.gen.go b/internal/test/server/server_moq.gen.go index aa22e65ca7..80bc73a9e0 100644 --- a/internal/test/server/server_moq.gen.go +++ b/internal/test/server/server_moq.gen.go @@ -14,46 +14,46 @@ var _ ServerInterface = &ServerInterfaceMock{} // ServerInterfaceMock is a mock implementation of ServerInterface. // -// func TestSomethingThatUsesServerInterface(t *testing.T) { +// func TestSomethingThatUsesServerInterface(t *testing.T) { // -// // make and configure a mocked ServerInterface -// mockedServerInterface := &ServerInterfaceMock{ -// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { -// panic("mock out the CreateResource method") -// }, -// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { -// panic("mock out the CreateResource2 method") -// }, -// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetEveryTypeOptional method") -// }, -// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetReservedKeyword method") -// }, -// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetResponseWithReference method") -// }, -// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { -// panic("mock out the GetSimple method") -// }, -// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { -// panic("mock out the GetWithArgs method") -// }, -// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { -// panic("mock out the GetWithContentType method") -// }, -// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { -// panic("mock out the GetWithReferences method") -// }, -// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { -// panic("mock out the UpdateResource3 method") -// }, -// } +// // make and configure a mocked ServerInterface +// mockedServerInterface := &ServerInterfaceMock{ +// CreateResourceFunc: func(w http.ResponseWriter, r *http.Request, argument string) { +// panic("mock out the CreateResource method") +// }, +// CreateResource2Func: func(w http.ResponseWriter, r *http.Request, inlineArgument int, params CreateResource2Params) { +// panic("mock out the CreateResource2 method") +// }, +// GetEveryTypeOptionalFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetEveryTypeOptional method") +// }, +// GetReservedKeywordFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetReservedKeyword method") +// }, +// GetResponseWithReferenceFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetResponseWithReference method") +// }, +// GetSimpleFunc: func(w http.ResponseWriter, r *http.Request) { +// panic("mock out the GetSimple method") +// }, +// GetWithArgsFunc: func(w http.ResponseWriter, r *http.Request, params GetWithArgsParams) { +// panic("mock out the GetWithArgs method") +// }, +// GetWithContentTypeFunc: func(w http.ResponseWriter, r *http.Request, contentType GetWithContentTypeParamsContentType) { +// panic("mock out the GetWithContentType method") +// }, +// GetWithReferencesFunc: func(w http.ResponseWriter, r *http.Request, globalArgument int64, argument string) { +// panic("mock out the GetWithReferences method") +// }, +// UpdateResource3Func: func(w http.ResponseWriter, r *http.Request, pFallthrough int) { +// panic("mock out the UpdateResource3 method") +// }, +// } // -// // use mockedServerInterface in code that requires ServerInterface -// // and then make assertions. +// // use mockedServerInterface in code that requires ServerInterface +// // and then make assertions. // -// } +// } type ServerInterfaceMock struct { // CreateResourceFunc mocks the CreateResource method. CreateResourceFunc func(w http.ResponseWriter, r *http.Request, argument string) @@ -208,7 +208,8 @@ func (mock *ServerInterfaceMock) CreateResource(w http.ResponseWriter, r *http.R // CreateResourceCalls gets all the calls that were made to CreateResource. // Check the length with: -// len(mockedServerInterface.CreateResourceCalls()) +// +// len(mockedServerInterface.CreateResourceCalls()) func (mock *ServerInterfaceMock) CreateResourceCalls() []struct { W http.ResponseWriter R *http.Request @@ -249,7 +250,8 @@ func (mock *ServerInterfaceMock) CreateResource2(w http.ResponseWriter, r *http. // CreateResource2Calls gets all the calls that were made to CreateResource2. // Check the length with: -// len(mockedServerInterface.CreateResource2Calls()) +// +// len(mockedServerInterface.CreateResource2Calls()) func (mock *ServerInterfaceMock) CreateResource2Calls() []struct { W http.ResponseWriter R *http.Request @@ -288,7 +290,8 @@ func (mock *ServerInterfaceMock) GetEveryTypeOptional(w http.ResponseWriter, r * // GetEveryTypeOptionalCalls gets all the calls that were made to GetEveryTypeOptional. // Check the length with: -// len(mockedServerInterface.GetEveryTypeOptionalCalls()) +// +// len(mockedServerInterface.GetEveryTypeOptionalCalls()) func (mock *ServerInterfaceMock) GetEveryTypeOptionalCalls() []struct { W http.ResponseWriter R *http.Request @@ -323,7 +326,8 @@ func (mock *ServerInterfaceMock) GetReservedKeyword(w http.ResponseWriter, r *ht // GetReservedKeywordCalls gets all the calls that were made to GetReservedKeyword. // Check the length with: -// len(mockedServerInterface.GetReservedKeywordCalls()) +// +// len(mockedServerInterface.GetReservedKeywordCalls()) func (mock *ServerInterfaceMock) GetReservedKeywordCalls() []struct { W http.ResponseWriter R *http.Request @@ -358,7 +362,8 @@ func (mock *ServerInterfaceMock) GetResponseWithReference(w http.ResponseWriter, // GetResponseWithReferenceCalls gets all the calls that were made to GetResponseWithReference. // Check the length with: -// len(mockedServerInterface.GetResponseWithReferenceCalls()) +// +// len(mockedServerInterface.GetResponseWithReferenceCalls()) func (mock *ServerInterfaceMock) GetResponseWithReferenceCalls() []struct { W http.ResponseWriter R *http.Request @@ -393,7 +398,8 @@ func (mock *ServerInterfaceMock) GetSimple(w http.ResponseWriter, r *http.Reques // GetSimpleCalls gets all the calls that were made to GetSimple. // Check the length with: -// len(mockedServerInterface.GetSimpleCalls()) +// +// len(mockedServerInterface.GetSimpleCalls()) func (mock *ServerInterfaceMock) GetSimpleCalls() []struct { W http.ResponseWriter R *http.Request @@ -430,7 +436,8 @@ func (mock *ServerInterfaceMock) GetWithArgs(w http.ResponseWriter, r *http.Requ // GetWithArgsCalls gets all the calls that were made to GetWithArgs. // Check the length with: -// len(mockedServerInterface.GetWithArgsCalls()) +// +// len(mockedServerInterface.GetWithArgsCalls()) func (mock *ServerInterfaceMock) GetWithArgsCalls() []struct { W http.ResponseWriter R *http.Request @@ -469,7 +476,8 @@ func (mock *ServerInterfaceMock) GetWithContentType(w http.ResponseWriter, r *ht // GetWithContentTypeCalls gets all the calls that were made to GetWithContentType. // Check the length with: -// len(mockedServerInterface.GetWithContentTypeCalls()) +// +// len(mockedServerInterface.GetWithContentTypeCalls()) func (mock *ServerInterfaceMock) GetWithContentTypeCalls() []struct { W http.ResponseWriter R *http.Request @@ -510,7 +518,8 @@ func (mock *ServerInterfaceMock) GetWithReferences(w http.ResponseWriter, r *htt // GetWithReferencesCalls gets all the calls that were made to GetWithReferences. // Check the length with: -// len(mockedServerInterface.GetWithReferencesCalls()) +// +// len(mockedServerInterface.GetWithReferencesCalls()) func (mock *ServerInterfaceMock) GetWithReferencesCalls() []struct { W http.ResponseWriter R *http.Request @@ -551,7 +560,8 @@ func (mock *ServerInterfaceMock) UpdateResource3(w http.ResponseWriter, r *http. // UpdateResource3Calls gets all the calls that were made to UpdateResource3. // Check the length with: -// len(mockedServerInterface.UpdateResource3Calls()) +// +// len(mockedServerInterface.UpdateResource3Calls()) func (mock *ServerInterfaceMock) UpdateResource3Calls() []struct { W http.ResponseWriter R *http.Request diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go index 06dbc6ca8d..124c057d2f 100644 --- a/internal/test/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -259,16 +259,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } @@ -561,7 +561,7 @@ type ReusableResponsesResponseObject interface { VisitReusableResponsesResponse(w http.ResponseWriter) error } -type ReusableResponses200JSONResponse = ReusableresponseJSONResponse +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) diff --git a/internal/test/strict-server/chi/server.go b/internal/test/strict-server/chi/server.go index 7c8ae2b763..5b7026809e 100644 --- a/internal/test/strict-server/chi/server.go +++ b/internal/test/strict-server/chi/server.go @@ -98,5 +98,5 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample } func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { - return ReusableResponses200JSONResponse{Body: *request.Body}, nil + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } diff --git a/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go index 3002b2ae04..46531bd8a4 100644 --- a/internal/test/strict-server/echo/server.gen.go +++ b/internal/test/strict-server/echo/server.gen.go @@ -387,7 +387,7 @@ type ReusableResponsesResponseObject interface { VisitReusableResponsesResponse(w http.ResponseWriter) error } -type ReusableResponses200JSONResponse = ReusableresponseJSONResponse +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) diff --git a/internal/test/strict-server/echo/server.go b/internal/test/strict-server/echo/server.go index 7c8ae2b763..5b7026809e 100644 --- a/internal/test/strict-server/echo/server.go +++ b/internal/test/strict-server/echo/server.go @@ -98,5 +98,5 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample } func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { - return ReusableResponses200JSONResponse{Body: *request.Body}, nil + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } diff --git a/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go index 5192a0dc5a..91ecba08f0 100644 --- a/internal/test/strict-server/gin/server.gen.go +++ b/internal/test/strict-server/gin/server.gen.go @@ -57,6 +57,7 @@ type ServerInterface interface { type ServerInterfaceWrapper struct { Handler ServerInterface HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } type MiddlewareFunc func(c *gin.Context) @@ -156,20 +157,20 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { var Header1 string n := len(valueList) if n != 1 { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for header1, got %d", n)}) + siw.ErrorHandler(c, fmt.Errorf("Expected one value for header1, got %d", n), http.StatusBadRequest) return } err = runtime.BindStyledParameterWithLocation("simple", false, "header1", runtime.ParamLocationHeader, valueList[0], &Header1) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter header1: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header1: %s", err), http.StatusBadRequest) return } params.Header1 = Header1 } else { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Header parameter header1 is required, but not found: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Header parameter header1 is required, but not found: %s", err), http.StatusBadRequest) return } @@ -178,13 +179,13 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { var Header2 int n := len(valueList) if n != 1 { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for header2, got %d", n)}) + siw.ErrorHandler(c, fmt.Errorf("Expected one value for header2, got %d", n), http.StatusBadRequest) return } err = runtime.BindStyledParameterWithLocation("simple", false, "header2", runtime.ParamLocationHeader, valueList[0], &Header2) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter header2: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter header2: %s", err), http.StatusBadRequest) return } @@ -201,8 +202,9 @@ func (siw *ServerInterfaceWrapper) HeadersExample(c *gin.Context) { // GinServerOptions provides options for the Gin server. type GinServerOptions struct { - BaseURL string - Middlewares []MiddlewareFunc + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } // RegisterHandlers creates http.Handler with routing matching OpenAPI spec. @@ -212,9 +214,19 @@ func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine { // RegisterHandlersWithOptions creates http.Handler with additional options func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { + + errorHandler := options.ErrorHandler + + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + wrapper := ServerInterfaceWrapper{ Handler: si, HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, } router.POST(options.BaseURL+"/json", wrapper.JSONExample) @@ -409,7 +421,7 @@ type ReusableResponsesResponseObject interface { VisitReusableResponsesResponse(w http.ResponseWriter) error } -type ReusableResponses200JSONResponse = ReusableresponseJSONResponse +type ReusableResponses200JSONResponse struct{ ReusableresponseJSONResponse } func (response ReusableResponses200JSONResponse) VisitReusableResponsesResponse(w http.ResponseWriter) error { w.Header().Set("header1", fmt.Sprint(response.Headers.Header1)) diff --git a/internal/test/strict-server/gin/server.go b/internal/test/strict-server/gin/server.go index 7c8ae2b763..5b7026809e 100644 --- a/internal/test/strict-server/gin/server.go +++ b/internal/test/strict-server/gin/server.go @@ -98,5 +98,5 @@ func (s StrictServer) HeadersExample(ctx context.Context, request HeadersExample } func (s StrictServer) ReusableResponses(ctx context.Context, request ReusableResponsesRequestObject) (ReusableResponsesResponseObject, error) { - return ReusableResponses200JSONResponse{Body: *request.Body}, nil + return ReusableResponses200JSONResponse{ReusableresponseJSONResponse: ReusableresponseJSONResponse{Body: *request.Body}}, nil } diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index c4f3847754..ed1afb03d6 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -114,6 +114,10 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { responseTypeSuffix = opts.OutputOptions.ResponseTypeSuffix } + if globalState.options.OutputOptions.ClientTypeName == "" { + globalState.options.OutputOptions.ClientTypeName = defaultClientTypeName + } + // This creates the golang templates text package TemplateFunctions["opts"] = func() Configuration { return globalState.options } t := template.New("oapi-codegen").Funcs(TemplateFunctions) diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index df8fa737fd..1400c3c740 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -81,6 +81,7 @@ type OutputOptions struct { ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` // Exclude from generation schemas with given names. Ignored when empty. ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types + ClientTypeName string `yaml:"client-type-name,omitempty"` // Override the default generated client type with the value } // UpdateDefaults sets reasonable default values for unset fields in Configuration diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 7ab0b816e7..314e89462d 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -513,6 +513,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { } else { op.OperationID = ToCamelCase(op.OperationID) } + op.OperationID = typeNamePrefix(op.OperationID) + op.OperationID // These are parameters defined for the specific path method that // we're iterating over. @@ -933,7 +934,7 @@ func GenerateClient(t *template.Template, ops []OperationDefinition) (string, er } // GenerateClientWithResponses generates a client which extends the basic client which does response -// unmarshaling. +// unmarshalling. func GenerateClientWithResponses(t *template.Template, ops []OperationDefinition) (string, error) { return GenerateTemplates([]string{"client-with-responses.tmpl"}, t, ops) } diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index f071cdfc65..5edf0479e6 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -165,7 +165,7 @@ type TypeDefinition struct { } // ResponseTypeDefinition is an extension of TypeDefinition, specifically for -// response unmarshaling in ClientWithResponses. +// response unmarshalling in ClientWithResponses. type ResponseTypeDefinition struct { TypeDefinition // The content type name where this is used, eg, application/json @@ -267,6 +267,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoType, err) } outSchema.GoType = typeName + outSchema.DefineViaAlias = true return outSchema, nil } diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index 90182cf9bd..b3945210fb 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -28,7 +28,7 @@ import ( const ( // These allow the case statements to be sorted later: - prefixLeastSpecific = "9" + prefixMostSpecific, prefixLessSpecific, prefixLeastSpecific = "3", "6", "9" ) var ( @@ -99,7 +99,7 @@ func genResponsePayload(operationID string) string { return buffer.String() } -// genResponseUnmarshal generates unmarshaling steps for structured response payloads +// genResponseUnmarshal generates unmarshalling steps for structured response payloads func genResponseUnmarshal(op *OperationDefinition) string { var handledCaseClauses = make(map[string]string) var unhandledCaseClauses = make(map[string]string) @@ -131,7 +131,7 @@ func genResponseUnmarshal(op *OperationDefinition) string { continue } - // If there is no content-type then we have no unmarshaling to do: + // If there is no content-type then we have no unmarshalling to do: if len(responseRef.Value.Content) == 0 { caseAction := "break // No content-type" caseClauseKey := "case " + getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) + ":" @@ -139,7 +139,7 @@ func genResponseUnmarshal(op *OperationDefinition) string { continue } - // If we made it this far then we need to handle unmarshaling for each content-type: + // If we made it this far then we need to handle unmarshalling for each content-type: sortedContentKeys := SortedContentKeys(responseRef.Value.Content) for _, contentTypeName := range sortedContentKeys { @@ -282,6 +282,7 @@ var TemplateFunctions = template.FuncMap{ "swaggerUriToGorillaUri": SwaggerUriToGorillaUri, "lcFirst": LowercaseFirstCharacter, "ucFirst": UppercaseFirstCharacter, + "ucFirstWithPkgName": UppercaseFirstCharacterWithPkgName, "camelCase": ToCamelCase, "genResponsePayload": genResponsePayload, "genResponseTypeName": genResponseTypeName, diff --git a/pkg/codegen/templates/additional-properties.tmpl b/pkg/codegen/templates/additional-properties.tmpl index 46b66c86e2..8a37647bf4 100644 --- a/pkg/codegen/templates/additional-properties.tmpl +++ b/pkg/codegen/templates/additional-properties.tmpl @@ -39,7 +39,7 @@ func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error { var fieldVal {{$addType}} err := json.Unmarshal(fieldBuf, &fieldVal) if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + return fmt.Errorf("error unmarshalling field %s: %w", fieldName, err) } a.AdditionalProperties[fieldName] = fieldVal } diff --git a/pkg/codegen/templates/chi/chi-middleware.tmpl b/pkg/codegen/templates/chi/chi-middleware.tmpl index 77a5c2a0d4..bb4e8f7715 100644 --- a/pkg/codegen/templates/chi/chi-middleware.tmpl +++ b/pkg/codegen/templates/chi/chi-middleware.tmpl @@ -25,7 +25,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} @@ -62,7 +62,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } @@ -101,7 +101,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} @@ -146,7 +146,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } @@ -204,16 +204,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 4cb85f0aca..a3fd2b6dd5 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -13,9 +13,11 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes return &ClientWithResponses{client}, nil } +{{$clientTypeName := opts.OutputOptions.ClientTypeName -}} + // WithBaseURL overrides the baseURL. func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { + return func(c *{{ $clientTypeName }}) error { newBaseURL, err := url.Parse(baseURL) if err != nil { return err diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index ffb62bbebe..58df443c76 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -8,8 +8,10 @@ type HttpRequestDoer interface { Do(req *http.Request) (*http.Response, error) } -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { +{{$clientTypeName := opts.OutputOptions.ClientTypeName -}} + +// {{ $clientTypeName }} which conforms to the OpenAPI3 specification for this service. +type {{ $clientTypeName }} struct { // The endpoint of the server conforming to this interface, with scheme, // https://api.deepmap.com for example. This can contain a path relative // to the server, such as https://api.deepmap.com/dev-test, and all the @@ -26,12 +28,12 @@ type Client struct { } // ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error +type ClientOption func(*{{ $clientTypeName }}) error -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { +// Creates a new {{ $clientTypeName }}, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*{{ $clientTypeName }}, error) { // create a client with sane default values - client := Client{ + client := {{ $clientTypeName }}{ Server: server, } // mutate client and add all optional params @@ -54,7 +56,7 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) { // 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 { + return func(c *{{ $clientTypeName }}) error { c.Client = doer return nil } @@ -63,7 +65,7 @@ func WithHTTPClient(doer HttpRequestDoer) ClientOption { // 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 { + return func(c *{{ $clientTypeName }}) error { c.RequestEditors = append(c.RequestEditors, fn) return nil } @@ -92,7 +94,7 @@ type ClientInterface interface { {{$pathParams := .PathParams -}} {{$opid := .OperationId -}} -func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) { +func (c *{{ $clientTypeName }}) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(c.Server{{genParamNames .PathParams}}{{if $hasParams}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}) if err != nil { return nil, err @@ -106,7 +108,7 @@ func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{ge {{range .Bodies}} {{if .IsSupportedByClient -}} -func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { +func (c *{{ $clientTypeName }}) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) { req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) if err != nil { return nil, err @@ -285,7 +287,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr {{end}}{{/* Range */}} -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { +func (c *{{ $clientTypeName }}) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { return err diff --git a/pkg/codegen/templates/echo/echo-wrappers.tmpl b/pkg/codegen/templates/echo/echo-wrappers.tmpl index 34025838d2..f2e4dbc099 100644 --- a/pkg/codegen/templates/echo/echo-wrappers.tmpl +++ b/pkg/codegen/templates/echo/echo-wrappers.tmpl @@ -14,7 +14,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{if .IsJson}} err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}}) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") } {{end}} {{if .IsStyled}} @@ -50,7 +50,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} @@ -75,7 +75,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") } {{end}} {{if .IsStyled}} @@ -105,7 +105,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshalling parameter '{{.ParamName}}' as JSON") } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} diff --git a/pkg/codegen/templates/gin/gin-register.tmpl b/pkg/codegen/templates/gin/gin-register.tmpl index 5362dd7ab6..188cbb83ea 100644 --- a/pkg/codegen/templates/gin/gin-register.tmpl +++ b/pkg/codegen/templates/gin/gin-register.tmpl @@ -2,6 +2,7 @@ type GinServerOptions struct { BaseURL string Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } // RegisterHandlers creates http.Handler with routing matching OpenAPI spec. @@ -11,13 +12,23 @@ func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine { // RegisterHandlersWithOptions creates http.Handler with additional options func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { -{{if .}}wrapper := ServerInterfaceWrapper{ -Handler: si, -HandlerMiddlewares: options.Middlewares, -} -{{end}} -{{range .}} -router.{{.Method }}(options.BaseURL+"{{.Path | swaggerUriToGinUri }}", wrapper.{{.OperationId}}) -{{end}} -return router + {{if .}} + errorHandler := options.ErrorHandler + + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + {{end}} + {{range .}} + router.{{.Method }}(options.BaseURL+"{{.Path | swaggerUriToGinUri }}", wrapper.{{.OperationId}}) + {{end}} + return router } diff --git a/pkg/codegen/templates/gin/gin-wrappers.tmpl b/pkg/codegen/templates/gin/gin-wrappers.tmpl index 59d03f5047..3c11bcdbe3 100644 --- a/pkg/codegen/templates/gin/gin-wrappers.tmpl +++ b/pkg/codegen/templates/gin/gin-wrappers.tmpl @@ -2,6 +2,7 @@ type ServerInterfaceWrapper struct { Handler ServerInterface HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) } type MiddlewareFunc func(c *gin.Context) @@ -24,14 +25,14 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { {{if .IsJson}} err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}}) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"}) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) return } {{end}} {{if .IsStyled}} err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", c.Param("{{.ParamName}}"), &{{$varName}}) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) return } {{end}} @@ -61,21 +62,23 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"}) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON: %s", err), http.StatusBadRequest) { return } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} }{{if .Required}} else { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"}) - return + if !siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) { + return + } }{{end}} {{end}} + {{if .IsStyled}} err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), ¶ms.{{.GoName}}) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) return } {{end}} @@ -89,7 +92,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { var {{.GoName}} {{.TypeDef}} n := len(valueList) if n != 1 { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)}) + siw.ErrorHandler(c, fmt.Errorf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest) return } @@ -100,7 +103,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"}) + siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) return } {{end}} @@ -108,7 +111,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { {{if .IsStyled}} err = runtime.BindStyledParameterWithLocation("{{.Style}}",{{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, valueList[0], &{{.GoName}}) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) return } {{end}} @@ -116,7 +119,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} } {{if .Required}}else { - c.JSON(http.StatusBadRequest, gin.H{"msg": fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err)}) + siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) return }{{end}} @@ -137,14 +140,14 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { var decoded string decoded, err := url.QueryUnescape(cookie.Value) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unescaping cookie parameter '{{.ParamName}}'"}) - return + siw.ErrorHandler(c, fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'"), http.StatusBadRequest) + return } err = json.Unmarshal([]byte(decoded), &value) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"}) - return + siw.ErrorHandler(c, fmt.Errorf("Error unmarshalling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest) + return } params.{{.GoName}} = {{if not .Required}}&{{end}}value @@ -154,8 +157,8 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { var value {{.TypeDef}} err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Invalid format for parameter {{.ParamName}}: %s"}) - return + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return } params.{{.GoName}} = {{if not .Required}}&{{end}}value {{end}} @@ -163,7 +166,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { } {{- if .Required}} else { - c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"}) + siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest) return } {{- end}} diff --git a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl index d660a0b690..a72713b3a6 100644 --- a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl +++ b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl @@ -25,7 +25,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(mux.Vars(r)["{{.ParamName}}"]), &{{$varName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} @@ -62,7 +62,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ var value {{.TypeDef}} err = json.Unmarshal([]byte(paramValue), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } @@ -101,7 +101,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } {{end}} @@ -146,7 +146,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ err = json.Unmarshal([]byte(decoded), &value) if err != nil { - siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err}) + siw.ErrorHandlerFunc(w, r, &UnmarshallingParamError{ParamName: "{{.ParamName}}", Err: err}) return } @@ -198,16 +198,16 @@ func (e *UnescapedCookieParamError) Unwrap() error { return e.Err } -type UnmarshalingParamError struct { +type UnmarshallingParamError struct { ParamName string Err error } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) } -func (e *UnmarshalingParamError) Unwrap() error { +func (e *UnmarshallingParamError) Unwrap() error { return e.Err } diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index 8a435babef..1911f56e8d 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -25,7 +25,7 @@ {{$hasHeaders := ne 0 (len .Headers) -}} {{$fixedStatusCode := .HasFixedStatusCode -}} {{$isRef := .IsRef -}} - {{$ref := .Ref | ucFirst -}} + {{$ref := .Ref | ucFirstWithPkgName -}} {{$headers := .Headers -}} {{if (and $hasHeaders (not $isRef)) -}} @@ -37,12 +37,13 @@ {{end}} {{range .Contents}} + {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} {{if and $fixedStatusCode $isRef -}} - type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response = {{$ref}}{{.NameTagOrContentType}}Response + type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response } {{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}} - type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} + type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{else -}} - type {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response struct { + type {{$receiverTypeName}} struct { Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}} {{if $hasHeaders -}} Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders @@ -62,7 +63,7 @@ } {{end}} - func (response {{$opid}}{{$statusCode}}{{.NameTagOrContentType}}Response) Visit{{$opid}}Response(w http.ResponseWriter) error { + func (response {{$receiverTypeName}}) Visit{{$opid}}Response(w http.ResponseWriter) error { {{range $headers -}} w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) {{end -}} diff --git a/pkg/codegen/templates/union.tmpl b/pkg/codegen/templates/union.tmpl index eb015be2b9..b758a82c64 100644 --- a/pkg/codegen/templates/union.tmpl +++ b/pkg/codegen/templates/union.tmpl @@ -100,10 +100,12 @@ } } {{range .Schema.Properties}} + {{if not .Required}}if t.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } + {{if not .Required}} }{{end}} {{end -}} b, err = json.Marshal(object) {{end -}} diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index 91b5e4d279..94e79d987b 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -104,6 +104,24 @@ func UppercaseFirstCharacter(str string) string { return string(runes) } +// Uppercase the first character in a identifier with pkg name. This assumes UTF-8, so we have +// to be careful with unicode, don't treat it as a byte array. +func UppercaseFirstCharacterWithPkgName(str string) string { + if str == "" { + return "" + } + + segs := strings.Split(str, ".") + var prefix string + if len(segs) == 2 { + prefix = segs[0] + "." + str = segs[1] + } + runes := []rune(str) + runes[0] = unicode.ToUpper(runes[0]) + return prefix + string(runes) +} + // LowercaseFirstCharacter Lowercases the first character in a string. This assumes UTF-8, so we have // to be careful with unicode, don't treat it as a byte array. func LowercaseFirstCharacter(str string) string { diff --git a/pkg/runtime/bindparam.go b/pkg/runtime/bindparam.go index 13d87066ea..4fc1b30d3d 100644 --- a/pkg/runtime/bindparam.go +++ b/pkg/runtime/bindparam.go @@ -68,7 +68,7 @@ func BindStyledParameterWithLocation(style string, explode bool, paramName strin // If the destination implements encoding.TextUnmarshaler we use it for binding if tu, ok := dest.(encoding.TextUnmarshaler); ok { if err := tu.UnmarshalText([]byte(value)); err != nil { - return fmt.Errorf("error unmarshaling '%s' text as %T: %s", value, dest, err) + return fmt.Errorf("error unmarshalling '%s' text as %T: %s", value, dest, err) } return nil @@ -82,7 +82,7 @@ func BindStyledParameterWithLocation(style string, explode bool, paramName strin if t.Kind() == reflect.Struct { // We've got a destination object, we'll create a JSON representation - // of the input value, and let the json library deal with the unmarshaling + // of the input value, and let the json library deal with the unmarshalling parts, err := splitStyledParameter(style, explode, true, paramName, value) if err != nil { return err @@ -233,7 +233,7 @@ func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error { // into the struct. func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error { // We've got a destination object, we'll create a JSON representation - // of the input value, and let the json library deal with the unmarshaling + // of the input value, and let the json library deal with the unmarshalling var fields []string if explode { fields = make([]string, len(parts)) @@ -462,7 +462,7 @@ func bindParamsToExplodedObject(paramName string, values url.Values, dest interf return true, BindStringToObject(values.Get(paramName), dest) } if t.Kind() != reflect.Struct { - return false, fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName) + return false, fmt.Errorf("unmarshalling query arg '%s' into wrong type", paramName) } fieldsPresent := false diff --git a/pkg/runtime/bindstring.go b/pkg/runtime/bindstring.go index e106b5ad75..72f74dd1bf 100644 --- a/pkg/runtime/bindstring.go +++ b/pkg/runtime/bindstring.go @@ -100,7 +100,7 @@ func BindStringToObject(src string, dst interface{}) error { case reflect.Array: if tu, ok := dst.(encoding.TextUnmarshaler); ok { if err := tu.UnmarshalText([]byte(src)); err != nil { - return fmt.Errorf("error unmarshaling '%s' text as %T: %s", src, dst, err) + return fmt.Errorf("error unmarshalling '%s' text as %T: %s", src, dst, err) } return nil diff --git a/pkg/runtime/styleparam.go b/pkg/runtime/styleparam.go index 2ba86d91c1..8f7e12927a 100644 --- a/pkg/runtime/styleparam.go +++ b/pkg/runtime/styleparam.go @@ -14,7 +14,9 @@ package runtime import ( + "bytes" "encoding" + "encoding/json" "errors" "fmt" "net/url" @@ -222,6 +224,27 @@ func styleStruct(style string, explode bool, paramName string, paramLocation Par return MarshalDeepObject(value, paramName) } + // If input has Marshaler, such as object has Additional Property or AnyOf, + // We use this Marshaler and convert into interface{} before styling. + if m, ok := value.(json.Marshaler); ok { + buf, err := m.MarshalJSON() + if err != nil { + return "", fmt.Errorf("failed to marshal input to JSON: %w", err) + } + e := json.NewDecoder(bytes.NewReader(buf)) + e.UseNumber() + var i2 interface{} + err = e.Decode(&i2) + if err != nil { + return "", fmt.Errorf("failed to unmarshal JSON: %w", err) + } + s, err := StyleParamWithLocation(style, explode, paramName, paramLocation, i2) + if err != nil { + return "", fmt.Errorf("error style JSON structure: %w", err) + } + return s, nil + } + // Otherwise, we need to build a dictionary of the struct's fields. Each // field may only be a primitive value. v := reflect.ValueOf(value) @@ -402,6 +425,28 @@ func primitiveToString(value interface{}) (string, error) { } case reflect.String: output = v.String() + case reflect.Struct: + // If input has Marshaler, such as object has Additional Property or AnyOf, + // We use this Marshaler and convert into interface{} before styling. + if m, ok := value.(json.Marshaler); ok { + buf, err := m.MarshalJSON() + if err != nil { + return "", fmt.Errorf("failed to marshal input to JSON: %w", err) + } + e := json.NewDecoder(bytes.NewReader(buf)) + e.UseNumber() + var i2 interface{} + err = e.Decode(&i2) + if err != nil { + return "", fmt.Errorf("failed to unmarshal JSON: %w", err) + } + output, err = primitiveToString(i2) + if err != nil { + return "", fmt.Errorf("error convert JSON structure: %w", err) + } + break + } + fallthrough default: v, ok := value.(fmt.Stringer) if !ok { diff --git a/pkg/testutil/request_helpers.go b/pkg/testutil/request_helpers.go index 633da59228..5ee107ee21 100644 --- a/pkg/testutil/request_helpers.go +++ b/pkg/testutil/request_helpers.go @@ -14,7 +14,7 @@ package testutil // This is a set of fluent request builders for tests, which help us to -// simplify constructing and unmarshaling test objects. For example, to post +// simplify constructing and unmarshalling test objects. For example, to post // a body and return a response, you would do something like: // // var body RequestBody