From 54712820271494c1545f5bf2d403e07461c128b4 Mon Sep 17 00:00:00 2001 From: Miguel Silva Date: Thu, 16 Apr 2026 15:42:18 +0100 Subject: [PATCH] feat: add Echo v5 server support and example - Add HandlerKindEchoV5 constant and IsValid() support in configuration.go - Add echo-v5 handler, middleware, and server templates - Add examples/server/echo-v5 standalone example with README - Add examples/server/test/echo-v5 test case wired into server_test.go - Add github.com/labstack/echo/v5 to examples/go.mod - Add echo-v5 to configuration-schema.json handler kind enum - Add echo-v5 router init test in integration_test.go --- configuration-schema.json | 2 +- examples/go.mod | 1 + examples/go.sum | 2 + examples/server/echo-v5/README.md | 70 + examples/server/echo-v5/api/gen.go | 724 +++++ examples/server/echo-v5/api/middleware.go | 30 + examples/server/echo-v5/api/service.go | 56 + examples/server/echo-v5/cfg.yml | 17 + examples/server/echo-v5/generate.go | 3 + examples/server/echo-v5/server/main.go | 67 + .../test/echo-v5/testcase/adapter.gen.go | 2652 +++++++++++++++++ examples/server/test/echo-v5/testcase/cfg.yml | 10 + examples/server/test/echo-v5/testcase/gen.go | 624 ++++ .../server/test/echo-v5/testcase/generate.go | 5 + .../server/test/echo-v5/testcase/service.go | 197 ++ examples/server/test/server_test.go | 7 + integration_test.go | 6 + pkg/codegen/configuration.go | 3 +- .../templates/handler/echo-v5/handler.tmpl | 99 + .../templates/handler/echo-v5/middleware.tmpl | 46 + .../templates/handler/echo-v5/server.tmpl | 95 + 21 files changed, 4714 insertions(+), 2 deletions(-) create mode 100644 examples/server/echo-v5/README.md create mode 100644 examples/server/echo-v5/api/gen.go create mode 100644 examples/server/echo-v5/api/middleware.go create mode 100644 examples/server/echo-v5/api/service.go create mode 100644 examples/server/echo-v5/cfg.yml create mode 100644 examples/server/echo-v5/generate.go create mode 100644 examples/server/echo-v5/server/main.go create mode 100644 examples/server/test/echo-v5/testcase/adapter.gen.go create mode 100644 examples/server/test/echo-v5/testcase/cfg.yml create mode 100644 examples/server/test/echo-v5/testcase/gen.go create mode 100644 examples/server/test/echo-v5/testcase/generate.go create mode 100644 examples/server/test/echo-v5/testcase/service.go create mode 100644 pkg/codegen/templates/handler/echo-v5/handler.tmpl create mode 100644 pkg/codegen/templates/handler/echo-v5/middleware.tmpl create mode 100644 pkg/codegen/templates/handler/echo-v5/server.tmpl diff --git a/configuration-schema.json b/configuration-schema.json index cabc1fa5..5c16cabb 100644 --- a/configuration-schema.json +++ b/configuration-schema.json @@ -276,7 +276,7 @@ }, "kind": { "type": "string", - "enum": ["beego", "chi", "echo", "fasthttp", "fiber", "gin", "goframe", "go-zero", "gorilla-mux", "hertz", "iris", "kratos", "std-http"], + "enum": ["beego", "chi", "echo", "echo-v5", "fasthttp", "fiber", "gin", "goframe", "go-zero", "gorilla-mux", "hertz", "iris", "kratos", "std-http"], "description": "Router/framework to generate for. Required." }, "models-package-alias": { diff --git a/examples/go.mod b/examples/go.mod index 1167bb62..db25a495 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -86,6 +86,7 @@ require ( github.com/kataras/tunnel v0.0.4 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/labstack/echo/v5 v5.1.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.10 // indirect diff --git a/examples/go.sum b/examples/go.sum index fcaa0a1c..ed17cf96 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -181,6 +181,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.15.0 h1:hoRTKWcnR5STXZFe9BmYun9AMTNeSbjHi2vtDuADJ24= github.com/labstack/echo/v4 v4.15.0/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c= +github.com/labstack/echo/v5 v5.1.0 h1:MvIRydoN+p9cx/zq8Lff6YXqUW2ZaEsOMISzEGSMrBI= +github.com/labstack/echo/v5 v5.1.0/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= diff --git a/examples/server/echo-v5/README.md b/examples/server/echo-v5/README.md new file mode 100644 index 00000000..0b168ac4 --- /dev/null +++ b/examples/server/echo-v5/README.md @@ -0,0 +1,70 @@ +# Echo v5 Server Example + +This example demonstrates server code generation using [Echo v5](https://github.com/labstack/echo), a high-performance, minimalist Go web framework. + +## Description + +- Echo v5 uses `echo.MiddlewareFunc` for middleware (handler signature changed to `func(c *echo.Context) error`) +- Path parameters are extracted via `c.PathParam("paramName")` +- Echo v5 uses a pointer receiver `*echo.Context` instead of the v4 interface +- Built-in middleware available: `middleware.Recover()`, `middleware.Logger()`, `middleware.CORS()`, etc. +- Graceful shutdown is handled via `echo.StartConfig.Start(ctx, e)` + +## Integrating with Existing Server + +If you already have an Echo v5 instance, register the generated routes: + +```go +import handler "your/module/api" + +svc := handler.NewService() +handler.NewRouter(e, svc) +``` + +## Running the Server + +```bash +go run ./server +``` + +The server starts on port 8080. + +## API Endpoints + +### Health Check + +```bash +curl http://localhost:8080/health +``` + +### List Users + +```bash +curl http://localhost:8080/users +``` + +With optional limit parameter: + +```bash +curl "http://localhost:8080/users?limit=10" +``` + +### Create User + +```bash +curl -X POST http://localhost:8080/users \ + -H "Content-Type: application/json" \ + -d '{"name": "John Doe", "email": "john@example.com"}' +``` + +### Get User by ID + +```bash +curl http://localhost:8080/users/123 +``` + +### Delete User + +```bash +curl -X DELETE http://localhost:8080/users/123 +``` diff --git a/examples/server/echo-v5/api/gen.go b/examples/server/echo-v5/api/gen.go new file mode 100644 index 00000000..12750890 --- /dev/null +++ b/examples/server/echo-v5/api/gen.go @@ -0,0 +1,724 @@ +// Code generated by oapi-codegen. DO NOT EDIT. + +package api + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" + "github.com/go-playground/validator/v10" + echo "github.com/labstack/echo/v5" +) + +// OapiErrorKind represents the type of error that occurred during request processing. +type OapiErrorKind int + +const ( + // OapiErrorKindParse indicates a parameter parsing error (invalid path/query/header parameter). + OapiErrorKindParse OapiErrorKind = iota + + // OapiErrorKindDecode indicates a request body decoding error (invalid JSON, form data, etc.). + OapiErrorKindDecode + + // OapiErrorKindValidation indicates a request validation error (failed schema validation). + OapiErrorKindValidation + + // OapiErrorKindService indicates a service/business logic error returned by the service implementation. + OapiErrorKindService +) + +// OapiHandlerError represents an error that occurred during request handling (parse, decode, validation). +// When no typed error response is configured in the OpenAPI spec, this error type is used. +// Custom error handlers can type-assert to this type to access error details. +type OapiHandlerError struct { + Kind OapiErrorKind + OperationID string + Message string + ParamName string + ParamLocation string +} + +func (e OapiHandlerError) Error() string { + return e.Message +} + +// OapiErrorResponse is the default JSON error response structure used by OapiDefaultErrorHandler. +type OapiErrorResponse struct { + Error string `json:"error"` + OperationID string `json:"operation_id,omitempty"` + ParamName string `json:"param_name,omitempty"` + ParamLocation string `json:"param_location,omitempty"` +} + +// OapiErrorHandler handles errors that occur during request processing. +// Implement this interface to customize error responses, logging, and metrics. +type OapiErrorHandler interface { + // HandleError writes an error response to w with the given status code. + // The err is either an OapiHandlerError (for parse/decode/validation errors) + // or a typed error matching the OpenAPI spec's error response schema. + HandleError(w http.ResponseWriter, r *http.Request, statusCode int, err error) +} + +// OapiDefaultErrorHandler provides the default error handling behavior. +// It writes JSON error responses. For OapiHandlerError, it uses OapiErrorResponse. +// For typed errors (from OpenAPI spec), it encodes them directly. +type OapiDefaultErrorHandler struct{} + +// HandleError implements OapiErrorHandler with default JSON error responses. +func (h *OapiDefaultErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, statusCode int, err error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + if handlerErr, ok := err.(OapiHandlerError); ok { + _ = json.NewEncoder(w).Encode(OapiErrorResponse{ + Error: handlerErr.Message, + OperationID: handlerErr.OperationID, + ParamName: handlerErr.ParamName, + ParamLocation: handlerErr.ParamLocation, + }) + return + } + + // Typed error from OpenAPI spec - encode directly + _ = json.NewEncoder(w).Encode(err) +} + +// ServiceInterface defines the service interface for business logic. +type ServiceInterface interface { + // HealthCheck Health check endpoint + HealthCheck(ctx context.Context) (*HealthCheckResponseData, error) + // ListUsers List all users + ListUsers(ctx context.Context, opts *ListUsersServiceRequestOptions) (*ListUsersResponseData, error) + // CreateUser Create a new user + CreateUser(ctx context.Context, opts *CreateUserServiceRequestOptions) (*CreateUserResponseData, error) + // GetUser Get a user by ID + GetUser(ctx context.Context, opts *GetUserServiceRequestOptions) (*GetUserResponseData, error) + // DeleteUser Delete a user + DeleteUser(ctx context.Context, opts *DeleteUserServiceRequestOptions) (*DeleteUserResponseData, error) +} + +// HTTPAdapter adapts the ServiceInterface to HTTP handlers. +// This struct is generated and should not be modified. +type HTTPAdapter struct { + svc ServiceInterface + errHandler OapiErrorHandler + jsonBodyDecoder runtime.JSONBodyDecoderFunc +} + +// NewHTTPAdapter creates a new HTTPAdapter wrapping the given service. +// If errHandler is nil, OapiDefaultErrorHandler is used. +func NewHTTPAdapter(svc ServiceInterface, errHandler OapiErrorHandler) *HTTPAdapter { + if errHandler == nil { + errHandler = &OapiDefaultErrorHandler{} + } + return &HTTPAdapter{svc: svc, errHandler: errHandler, jsonBodyDecoder: runtime.DecodeJSONBody} +} + +// HealthCheck handles GET /health +func (a *HTTPAdapter) HealthCheck(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Call business logic + resp, err := a.svc.HealthCheck(ctx) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// ListUsers handles GET /users +func (a *HTTPAdapter) ListUsers(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &ListUsersServiceRequestOptions{} + opts.RawRequest = r + + // Parse query parameters + queryParams := &ListUsersQuery{} + query := r.URL.Query() + if queryParamLimitStr := query.Get("limit"); queryParamLimitStr != "" { + queryParamLimit, err := runtime.ParseString[int](queryParamLimitStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "ListUsers", + Message: err.Error(), + ParamName: "limit", + ParamLocation: "query", + }) + return + } + queryParams.Limit = &queryParamLimit + } + opts.Query = queryParams + + // Call business logic + resp, err := a.svc.ListUsers(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// CreateUser handles POST /users +func (a *HTTPAdapter) CreateUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &CreateUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body CreateUserBody + if err := a.jsonBodyDecoder(r.Body, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "CreateUser", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.CreateUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetUser handles GET /users/{id} +func (a *HTTPAdapter) GetUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetUserPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// DeleteUser handles DELETE /users/{id} +func (a *HTTPAdapter) DeleteUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &DeleteUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &DeleteUserPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.DeleteUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 204 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + w.WriteHeader(status) +} + +// RouterOption is a function that configures the router. +type RouterOption func(*routerConfig) + +type routerConfig struct { + middlewares []echo.MiddlewareFunc + errHandler OapiErrorHandler + jsonBodyDecoder runtime.JSONBodyDecoderFunc +} + +// WithMiddleware adds middleware to the router. +func WithMiddleware(mw echo.MiddlewareFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.middlewares = append(cfg.middlewares, mw) + } +} + +// WithErrorHandler sets a custom error handler for the router. +// If not set, OapiDefaultErrorHandler is used. +func WithErrorHandler(h OapiErrorHandler) RouterOption { + return func(cfg *routerConfig) { + cfg.errHandler = h + } +} + +// WithJSONBodyDecoder sets a custom function for decoding JSON request bodies. +// If not set, runtime.DecodeJSONBody is used. +func WithJSONBodyDecoder(fn runtime.JSONBodyDecoderFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.jsonBodyDecoder = fn + } +} + +// NewRouter registers routes on the given Echo instance with the service implementation. +func NewRouter(e *echo.Echo, svc ServiceInterface, opts ...RouterOption) { + cfg := &routerConfig{} + for _, opt := range opts { + opt(cfg) + } + + // Apply middleware to all routes + for _, mw := range cfg.middlewares { + e.Use(mw) + } + + adapter := NewHTTPAdapter(svc, cfg.errHandler) + if cfg.jsonBodyDecoder != nil { + adapter.jsonBodyDecoder = cfg.jsonBodyDecoder + } + e.GET("/health", func(c *echo.Context) error { + adapter.HealthCheck(c.Response(), c.Request()) + return nil + }) + e.GET("/users", func(c *echo.Context) error { + adapter.ListUsers(c.Response(), c.Request()) + return nil + }) + e.POST("/users", func(c *echo.Context) error { + adapter.CreateUser(c.Response(), c.Request()) + return nil + }) + e.GET("/users/:id", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.GetUser(c.Response(), c.Request()) + return nil + }) + e.DELETE("/users/:id", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.DeleteUser(c.Response(), c.Request()) + return nil + }) +} + +type GetUserPath struct { + ID string `json:"id" validate:"required"` +} + +func (g GetUserPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type DeleteUserPath struct { + ID string `json:"id" validate:"required"` +} + +func (d DeleteUserPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(d)) +} + +type CreateUserBody = CreateUserRequest + +type ListUsersQuery struct { + Limit *int `json:"limit,omitempty"` +} + +// HealthCheckResponseData wraps the success response with optional headers and status override. +type HealthCheckResponseData struct { + Body *HealthCheckResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewHealthCheckResponseData creates a new HealthCheckResponseData with the given body. +func NewHealthCheckResponseData(body *HealthCheckResponse) *HealthCheckResponseData { + return &HealthCheckResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *HealthCheckResponseData) WithHeaders(h http.Header) *HealthCheckResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *HealthCheckResponseData) WithStatus(code int) *HealthCheckResponseData { + r.Status = code + return r +} + +// ListUsersResponseData wraps the success response with optional headers and status override. +type ListUsersResponseData struct { + Body *ListUsersResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewListUsersResponseData creates a new ListUsersResponseData with the given body. +func NewListUsersResponseData(body *ListUsersResponse) *ListUsersResponseData { + return &ListUsersResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ListUsersResponseData) WithHeaders(h http.Header) *ListUsersResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ListUsersResponseData) WithStatus(code int) *ListUsersResponseData { + r.Status = code + return r +} + +// CreateUserResponseData wraps the success response with optional headers and status override. +type CreateUserResponseData struct { + Body *CreateUserResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewCreateUserResponseData creates a new CreateUserResponseData with the given body. +func NewCreateUserResponseData(body *CreateUserResponse) *CreateUserResponseData { + return &CreateUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *CreateUserResponseData) WithHeaders(h http.Header) *CreateUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *CreateUserResponseData) WithStatus(code int) *CreateUserResponseData { + r.Status = code + return r +} + +// GetUserResponseData wraps the success response with optional headers and status override. +type GetUserResponseData struct { + Body *GetUserResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetUserResponseData creates a new GetUserResponseData with the given body. +func NewGetUserResponseData(body *GetUserResponse) *GetUserResponseData { + return &GetUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetUserResponseData) WithHeaders(h http.Header) *GetUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetUserResponseData) WithStatus(code int) *GetUserResponseData { + r.Status = code + return r +} + +// DeleteUserResponseData wraps the success response with optional headers and status override. +type DeleteUserResponseData struct { + Body *struct{} + Headers http.Header + Status int // 0 = use default (204) +} + +// NewDeleteUserResponseData creates a new DeleteUserResponseData with the given body. +func NewDeleteUserResponseData(body *struct{}) *DeleteUserResponseData { + return &DeleteUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *DeleteUserResponseData) WithHeaders(h http.Header) *DeleteUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *DeleteUserResponseData) WithStatus(code int) *DeleteUserResponseData { + r.Status = code + return r +} + +type HealthCheckResponse = HealthStatus + +type ListUsersResponse []User + +type CreateUserResponse = User + +type CreateUserErrorResponse = Error + +type GetUserResponse = User + +type GetUserErrorResponse = Error + +// ListUsersServiceRequestOptions holds all parameters for the ListUsers operation. +type ListUsersServiceRequestOptions struct { + Query *ListUsersQuery + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *ListUsersServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// CreateUserServiceRequestOptions holds all parameters for the CreateUser operation. +type CreateUserServiceRequestOptions struct { + Body *CreateUserBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *CreateUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetUserServiceRequestOptions holds all parameters for the GetUser operation. +type GetUserServiceRequestOptions struct { + PathParams *GetUserPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// DeleteUserServiceRequestOptions holds all parameters for the DeleteUser operation. +type DeleteUserServiceRequestOptions struct { + PathParams *DeleteUserPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *DeleteUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +type HealthStatus struct { + Status string `json:"status" validate:"required"` +} + +func (h HealthStatus) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(h)) +} + +type User struct { + ID string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` +} + +func (u User) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(u)) +} + +type CreateUserRequest struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` +} + +func (c CreateUserRequest) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type Error struct { + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +func (s Error) Error() string { + return "unmapped client error" +} + +var typesValidator *validator.Validate + +func init() { + typesValidator = validator.New(validator.WithRequiredStructEnabled()) + runtime.RegisterCustomTypeFunc(typesValidator) +} diff --git a/examples/server/echo-v5/api/middleware.go b/examples/server/echo-v5/api/middleware.go new file mode 100644 index 00000000..cc1a7973 --- /dev/null +++ b/examples/server/echo-v5/api/middleware.go @@ -0,0 +1,30 @@ +// Package api This file is generated ONCE as a starting point and will NOT be overwritten. +// Modify it freely to add your middleware logic. +// To regenerate, delete this file or set generate.handler.output.overwrite: true in config. +// +// Echo provides many built-in middleware: middleware.Logger(), middleware.Recover(), +// middleware.RequestID(), middleware.CORS(), middleware.Timeout(), etc. +// See: https://echo.labstack.com/docs/middleware +// +// This file shows how to write custom middleware using echo.MiddlewareFunc. +package api + +import ( + "log" + + "github.com/labstack/echo/v5" +) + +// ExampleMiddleware demonstrates a custom echo.MiddlewareFunc. +// It logs before and after each request. +func ExampleMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + log.Printf("before: %s %s", c.Request().Method, c.Request().URL.Path) + err := next(c) + resp, _ := echo.UnwrapResponse(c.Response()) + log.Printf("after: %s %s status=%d", c.Request().Method, c.Request().URL.Path, resp.Status) + return err + } + } +} diff --git a/examples/server/echo-v5/api/service.go b/examples/server/echo-v5/api/service.go new file mode 100644 index 00000000..ac8c4cad --- /dev/null +++ b/examples/server/echo-v5/api/service.go @@ -0,0 +1,56 @@ +// Package api This file is generated ONCE as a starting point and will NOT be overwritten. +// Modify it freely to add your business logic. +// To regenerate, delete this file or set generate.handler.output.overwrite: true in config. +package api + +import ( + "context" +) + +// Service implements the ServiceInterface. +// Add your dependencies here (database, clients, etc.) +type Service struct { +} + +// NewService creates a new Service. +func NewService() *Service { + return &Service{} +} + +// Ensure Service implements ServiceInterface. +var _ ServiceInterface = (*Service)(nil) + +// HealthCheck handles GET /health +// Health check endpoint +func (s *Service) HealthCheck(ctx context.Context) (*HealthCheckResponseData, error) { + // TODO: Implement your business logic here + return NewHealthCheckResponseData(new(HealthCheckResponse)), nil +} + +// ListUsers handles GET /users +// List all users +func (s *Service) ListUsers(ctx context.Context, opts *ListUsersServiceRequestOptions) (*ListUsersResponseData, error) { + // TODO: Implement your business logic here + return NewListUsersResponseData(new(ListUsersResponse)), nil +} + +// CreateUser handles POST /users +// Create a new user +func (s *Service) CreateUser(ctx context.Context, opts *CreateUserServiceRequestOptions) (*CreateUserResponseData, error) { + // TODO: Implement your business logic here + return NewCreateUserResponseData(new(CreateUserResponse)), nil +} + +// GetUser handles GET /users/{id} +// Get a user by ID +func (s *Service) GetUser(ctx context.Context, opts *GetUserServiceRequestOptions) (*GetUserResponseData, error) { + // TODO: Implement your business logic here + return NewGetUserResponseData(new(GetUserResponse)), nil +} + +// DeleteUser handles DELETE /users/{id} +// Delete a user +func (s *Service) DeleteUser(ctx context.Context, opts *DeleteUserServiceRequestOptions) (*DeleteUserResponseData, error) { + // TODO: Implement your business logic here + return NewDeleteUserResponseData(nil), nil +} diff --git a/examples/server/echo-v5/cfg.yml b/examples/server/echo-v5/cfg.yml new file mode 100644 index 00000000..d496db53 --- /dev/null +++ b/examples/server/echo-v5/cfg.yml @@ -0,0 +1,17 @@ +# yaml-language-server: $schema=../../../../configuration-schema.json +package: api +output: + directory: api + use-single-file: true +error-mapping: + CreateOrderErrorResponse: message +generate: + handler: + kind: echo-v5 + output: + overwrite: true + service: {} + middleware: {} + server: + directory: server + handler-package: github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/echo-v5/api diff --git a/examples/server/echo-v5/generate.go b/examples/server/echo-v5/generate.go new file mode 100644 index 00000000..b1a6fc4e --- /dev/null +++ b/examples/server/echo-v5/generate.go @@ -0,0 +1,3 @@ +package echov5 + +//go:generate go run github.com/doordash-oss/oapi-codegen-dd/v3/cmd/oapi-codegen -config cfg.yml ../api.yml diff --git a/examples/server/echo-v5/server/main.go b/examples/server/echo-v5/server/main.go new file mode 100644 index 00000000..aae1128c --- /dev/null +++ b/examples/server/echo-v5/server/main.go @@ -0,0 +1,67 @@ +// Package main - This file is generated ONCE as a starting point and will NOT be overwritten. +// Modify it freely to customize your server setup. +// To regenerate, delete this file or set generate.handler.output.overwrite: true in config. +package main + +import ( + "context" + "log" + "net/http" + "os/signal" + "syscall" + "time" + + handler "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/echo-v5/api" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" +) + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + // Create Echo instance + e := echo.New() + + // Add Echo built-in middleware + e.Use(middleware.Recover()) + e.Use(middleware.RequestID()) + e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + LogMethod: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + log.Printf("%s %s %d", v.Method, v.URI, v.Status) + return nil + }, + })) + e.Use(middleware.CORS("*")) + e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ + Timeout: 30 * time.Second, + })) + + // Add custom middleware from generated scaffold + e.Use(handler.ExampleMiddleware()) + + // Create your service implementation + svc := handler.NewService() + + // Register routes + handler.NewRouter(e, svc) + + // Start server with graceful shutdown + sc := echo.StartConfig{ + HideBanner: true, + Address: ":8080", + BeforeServeFunc: func(s *http.Server) error { + s.ReadTimeout = 5 * time.Second + s.WriteTimeout = 30 * time.Second + s.IdleTimeout = 120 * time.Second + return nil + }, + } + log.Printf("Starting server on :%d", 8080) + if err := sc.Start(ctx, e); err != nil { + log.Printf("Server stopped: %v", err) + } +} diff --git a/examples/server/test/echo-v5/testcase/adapter.gen.go b/examples/server/test/echo-v5/testcase/adapter.gen.go new file mode 100644 index 00000000..fb8e4fd5 --- /dev/null +++ b/examples/server/test/echo-v5/testcase/adapter.gen.go @@ -0,0 +1,2652 @@ +// Code generated by oapi-codegen. DO NOT EDIT. + +package testcase + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" + echo "github.com/labstack/echo/v5" +) + +// OapiErrorKind represents the type of error that occurred during request processing. +type OapiErrorKind int + +const ( + // OapiErrorKindParse indicates a parameter parsing error (invalid path/query/header parameter). + OapiErrorKindParse OapiErrorKind = iota + + // OapiErrorKindDecode indicates a request body decoding error (invalid JSON, form data, etc.). + OapiErrorKindDecode + + // OapiErrorKindValidation indicates a request validation error (failed schema validation). + OapiErrorKindValidation + + // OapiErrorKindService indicates a service/business logic error returned by the service implementation. + OapiErrorKindService +) + +// OapiHandlerError represents an error that occurred during request handling (parse, decode, validation). +// When no typed error response is configured in the OpenAPI spec, this error type is used. +// Custom error handlers can type-assert to this type to access error details. +type OapiHandlerError struct { + Kind OapiErrorKind + OperationID string + Message string + ParamName string + ParamLocation string +} + +func (e OapiHandlerError) Error() string { + return e.Message +} + +// OapiErrorResponse is the default JSON error response structure used by OapiDefaultErrorHandler. +type OapiErrorResponse struct { + Error string `json:"error"` + OperationID string `json:"operation_id,omitempty"` + ParamName string `json:"param_name,omitempty"` + ParamLocation string `json:"param_location,omitempty"` +} + +// OapiErrorHandler handles errors that occur during request processing. +// Implement this interface to customize error responses, logging, and metrics. +type OapiErrorHandler interface { + // HandleError writes an error response to w with the given status code. + // The err is either an OapiHandlerError (for parse/decode/validation errors) + // or a typed error matching the OpenAPI spec's error response schema. + HandleError(w http.ResponseWriter, r *http.Request, statusCode int, err error) +} + +// OapiDefaultErrorHandler provides the default error handling behavior. +// It writes JSON error responses. For OapiHandlerError, it uses OapiErrorResponse. +// For typed errors (from OpenAPI spec), it encodes them directly. +type OapiDefaultErrorHandler struct{} + +// HandleError implements OapiErrorHandler with default JSON error responses. +func (h *OapiDefaultErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, statusCode int, err error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + if handlerErr, ok := err.(OapiHandlerError); ok { + _ = json.NewEncoder(w).Encode(OapiErrorResponse{ + Error: handlerErr.Message, + OperationID: handlerErr.OperationID, + ParamName: handlerErr.ParamName, + ParamLocation: handlerErr.ParamLocation, + }) + return + } + + // Typed error from OpenAPI spec - encode directly + _ = json.NewEncoder(w).Encode(err) +} + +// ServiceInterface defines the service interface for business logic. +type ServiceInterface interface { + // HealthCheck Health check endpoint + HealthCheck(ctx context.Context) (*HealthCheckResponseData, error) + // ListUsers List all users + ListUsers(ctx context.Context, opts *ListUsersServiceRequestOptions) (*ListUsersResponseData, error) + // CreateUser Create a new user via JSON + CreateUser(ctx context.Context, opts *CreateUserServiceRequestOptions) (*CreateUserResponseData, error) + // ImportUsers Import users from CSV file + ImportUsers(ctx context.Context, opts *ImportUsersServiceRequestOptions) (*ImportUsersResponseData, error) + // GetUser Get a user by ID + GetUser(ctx context.Context, opts *GetUserServiceRequestOptions) (*GetUserResponseData, error) + // DeleteUser Delete a user + DeleteUser(ctx context.Context, opts *DeleteUserServiceRequestOptions) (*DeleteUserResponseData, error) + // GetUserAvatar Get user avatar image + GetUserAvatar(ctx context.Context, opts *GetUserAvatarServiceRequestOptions) (*GetUserAvatarResponseData, error) + // UploadUserAvatar Upload user avatar + UploadUserAvatar(ctx context.Context, opts *UploadUserAvatarServiceRequestOptions) (*UploadUserAvatarResponseData, error) + // SubmitContactForm Submit contact form + SubmitContactForm(ctx context.Context, opts *SubmitContactFormServiceRequestOptions) (*SubmitContactFormResponseData, error) + // CreateNote Create a note from plain text + CreateNote(ctx context.Context, opts *CreateNoteServiceRequestOptions) (*CreateNoteResponseData, error) + // ProcessXMLData Process XML data (demonstrates custom content type handling) + ProcessXMLData(ctx context.Context, opts *ProcessXMLDataServiceRequestOptions) (*ProcessXMLDataResponseData, error) + // ExportData Export all data as binary archive + ExportData(ctx context.Context) (*ExportDataResponseData, error) + // GetOAuthToken Get OAuth token (form-encoded response) + GetOAuthToken(ctx context.Context, opts *GetOAuthTokenServiceRequestOptions) (*GetOAuthTokenResponseData, error) + // GetItemsByType Get items by type (tests reserved Go keyword as path param) + GetItemsByType(ctx context.Context, opts *GetItemsByTypeServiceRequestOptions) (*GetItemsByTypeResponseData, error) + // Search Search with union type response (oneOf) + Search(ctx context.Context, opts *SearchServiceRequestOptions) (*SearchResponseData, error) + // GetStatus Get status (uses reusable response) + GetStatus(ctx context.Context) (*GetStatusResponseData, error) + // UploadImage Upload image (wildcard content type) + UploadImage(ctx context.Context, opts *UploadImageServiceRequestOptions) (*UploadImageResponseData, error) + // ListProducts List products with various query param types + ListProducts(ctx context.Context, opts *ListProductsServiceRequestOptions) (*ListProductsResponseData, error) + // GetCategory Get a category by ID (integer path param) + GetCategory(ctx context.Context, opts *GetCategoryServiceRequestOptions) (*GetCategoryResponseData, error) + // GetItemsByStatus Get items by type and rating (string + number path params) + GetItemsByStatus(ctx context.Context, opts *GetItemsByStatusServiceRequestOptions) (*GetItemsByStatusResponseData, error) + // GetUserPost Get a specific post by a user + GetUserPost(ctx context.Context, opts *GetUserPostServiceRequestOptions) (*GetUserPostResponseData, error) + // CreateOrder Create an order (demonstrates typed error responses) + CreateOrder(ctx context.Context, opts *CreateOrderServiceRequestOptions) (*CreateOrderResponseData, error) + // CreateCompany Create a company with nested address + CreateCompany(ctx context.Context, opts *CreateCompanyServiceRequestOptions) (*CreateCompanyResponseData, error) +} + +// HTTPAdapter adapts the ServiceInterface to HTTP handlers. +// This struct is generated and should not be modified. +type HTTPAdapter struct { + svc ServiceInterface + errHandler OapiErrorHandler + jsonBodyDecoder runtime.JSONBodyDecoderFunc +} + +// NewHTTPAdapter creates a new HTTPAdapter wrapping the given service. +// If errHandler is nil, OapiDefaultErrorHandler is used. +func NewHTTPAdapter(svc ServiceInterface, errHandler OapiErrorHandler) *HTTPAdapter { + if errHandler == nil { + errHandler = &OapiDefaultErrorHandler{} + } + return &HTTPAdapter{svc: svc, errHandler: errHandler, jsonBodyDecoder: runtime.DecodeJSONBody} +} + +// HealthCheck handles GET /health +func (a *HTTPAdapter) HealthCheck(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Call business logic + resp, err := a.svc.HealthCheck(ctx) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "text/plain") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _, _ = fmt.Fprintf(w, "%v", *resp.Body) + } +} + +// ListUsers handles GET /users +func (a *HTTPAdapter) ListUsers(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &ListUsersServiceRequestOptions{} + opts.RawRequest = r + + // Parse query parameters + queryParams := &ListUsersQuery{} + query := r.URL.Query() + if queryParamLimitStr := query.Get("limit"); queryParamLimitStr != "" { + queryParamLimit, err := runtime.ParseString[int](queryParamLimitStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "ListUsers", + Message: err.Error(), + ParamName: "limit", + ParamLocation: "query", + }) + return + } + queryParams.Limit = &queryParamLimit + } + opts.Query = queryParams + + // Parse header parameters + headerParams := &ListUsersHeaders{} + headers := r.Header + if headerValues := headers[http.CanonicalHeaderKey("X-Request-ID")]; len(headerValues) > 0 { + headerParamXRequestID := headerValues[0] + headerParams.XRequestID = headerParamXRequestID + } + opts.Header = headerParams + + // Call business logic + resp, err := a.svc.ListUsers(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// CreateUser handles POST /users +func (a *HTTPAdapter) CreateUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &CreateUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body CreateUserBody + if err := a.jsonBodyDecoder(r.Body, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "CreateUser", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.CreateUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// ImportUsers handles POST /users/import +func (a *HTTPAdapter) ImportUsers(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &ImportUsersServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + // Limit request body size to prevent memory exhaustion (gosec G120) + r.Body = http.MaxBytesReader(w, r.Body, 32<<20) + if err := r.ParseMultipartForm(32 << 20); err != nil { // #nosec G120 -- body is bounded by MaxBytesReader above + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "ImportUsers", + Message: err.Error(), + }) + return + } + var body ImportUsersBody + if fileHeaders := r.MultipartForm.File["file"]; len(fileHeaders) > 0 { + body.File.InitFromMultipart(fileHeaders[0]) + } + if values := r.MultipartForm.Value["overwrite"]; len(values) > 0 { + + if v, err := runtime.ParseString[bool](values[0]); err == nil { + body.Overwrite = &v + } + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.ImportUsers(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetUser handles GET /users/{id} +func (a *HTTPAdapter) GetUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetUserPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// DeleteUser handles DELETE /users/{id} +func (a *HTTPAdapter) DeleteUser(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &DeleteUserServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &DeleteUserPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.DeleteUser(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 204 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + w.WriteHeader(status) +} + +// GetUserAvatar handles GET /users/{id}/avatar +func (a *HTTPAdapter) GetUserAvatar(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetUserAvatarServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetUserAvatarPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetUserAvatar(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + var errResp *GetUserAvatarErrorResponse + if errors.As(err, &errResp) { + code = 404 + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/octet-stream") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + data, err := resp.Body.Bytes() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + _, _ = w.Write(data) + } +} + +// UploadUserAvatar handles PUT /users/{id}/avatar +func (a *HTTPAdapter) UploadUserAvatar(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &UploadUserAvatarServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &UploadUserAvatarPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + opts.PathParams = pathParams + // Parse request body + defer r.Body.Close() + + // Call business logic + resp, err := a.svc.UploadUserAvatar(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 204 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + w.WriteHeader(status) +} + +// SubmitContactForm handles POST /contact +func (a *HTTPAdapter) SubmitContactForm(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &SubmitContactFormServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body SubmitContactFormBody + formBytes, err := io.ReadAll(r.Body) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "SubmitContactForm", + Message: err.Error(), + }) + return + } + jsonBytes, err := runtime.ConvertFormFields(formBytes) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "SubmitContactForm", + Message: err.Error(), + }) + return + } + if err := json.Unmarshal(jsonBytes, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "SubmitContactForm", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.SubmitContactForm(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// CreateNote handles POST /notes +func (a *HTTPAdapter) CreateNote(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &CreateNoteServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "CreateNote", + Message: err.Error(), + }) + return + } + body := CreateNoteBody(string(bodyBytes)) + opts.Body = &body + + // Call business logic + resp, err := a.svc.CreateNote(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "text/plain") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _, _ = fmt.Fprintf(w, "%v", *resp.Body) + } +} + +// ProcessXMLData handles POST /xml-data +func (a *HTTPAdapter) ProcessXMLData(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &ProcessXMLDataServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + + // Call business logic + resp, err := a.svc.ProcessXMLData(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/xml") + } + + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _, _ = w.Write(resp.Body) + } +} + +// ExportData handles GET /export +func (a *HTTPAdapter) ExportData(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Call business logic + resp, err := a.svc.ExportData(ctx) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/octet-stream") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + data, err := resp.Body.Bytes() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + _, _ = w.Write(data) + } +} + +// GetOAuthToken handles POST /oauth/token +func (a *HTTPAdapter) GetOAuthToken(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetOAuthTokenServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body GetOAuthTokenBody + formBytes, err := io.ReadAll(r.Body) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "GetOAuthToken", + Message: err.Error(), + }) + return + } + jsonBytes, err := runtime.ConvertFormFields(formBytes) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "GetOAuthToken", + Message: err.Error(), + }) + return + } + if err := json.Unmarshal(jsonBytes, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "GetOAuthToken", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.GetOAuthToken(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + formData, err := runtime.EncodeFormFields(resp.Body, nil) + if err == nil { + _, _ = w.Write([]byte(formData)) + } + } +} + +// GetItemsByType handles GET /items/{type} +func (a *HTTPAdapter) GetItemsByType(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetItemsByTypeServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetItemsByTypePath{} + pathParamTypeStr := r.PathValue("type") + pathParams.Type = pathParamTypeStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetItemsByType(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// Search handles GET /search +func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &SearchServiceRequestOptions{} + opts.RawRequest = r + + // Parse query parameters + queryParams := &SearchQuery{} + query := r.URL.Query() + if queryParamQStr := query.Get("q"); queryParamQStr != "" { + queryParamQ := queryParamQStr + queryParams.Q = queryParamQ + } + opts.Query = queryParams + + // Call business logic + resp, err := a.svc.Search(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetStatus handles GET /status +func (a *HTTPAdapter) GetStatus(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Call business logic + resp, err := a.svc.GetStatus(ctx) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// UploadImage handles POST /images +func (a *HTTPAdapter) UploadImage(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &UploadImageServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + + // Call business logic + resp, err := a.svc.UploadImage(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// ListProducts handles GET /products +func (a *HTTPAdapter) ListProducts(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &ListProductsServiceRequestOptions{} + opts.RawRequest = r + + // Parse query parameters + queryParams := &ListProductsQuery{} + query := r.URL.Query() + + if values, ok := query["ids"]; ok { + queryParams.Ids = values + } + + if values, ok := query["tags"]; ok { + queryParams.Tags = values + } + + if values, ok := query["categoryIds"]; ok { + parsed, err := runtime.ParseStringSlice[int](values) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "ListProducts", + Message: err.Error(), + ParamName: "categoryIds", + ParamLocation: "query", + }) + return + } + queryParams.CategoryIds = parsed + } + if queryParamMinPriceStr := query.Get("minPrice"); queryParamMinPriceStr != "" { + queryParamMinPrice, err := runtime.ParseString[float32](queryParamMinPriceStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "ListProducts", + Message: err.Error(), + ParamName: "minPrice", + ParamLocation: "query", + }) + return + } + queryParams.MinPrice = &queryParamMinPrice + } + if queryParamActiveStr := query.Get("active"); queryParamActiveStr != "" { + queryParamActive, err := runtime.ParseString[bool](queryParamActiveStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "ListProducts", + Message: err.Error(), + ParamName: "active", + ParamLocation: "query", + }) + return + } + queryParams.Active = &queryParamActive + } + opts.Query = queryParams + + // Call business logic + resp, err := a.svc.ListProducts(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetCategory handles GET /categories/{categoryId} +func (a *HTTPAdapter) GetCategory(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetCategoryServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetCategoryPath{} + pathParamCategoryIDStr := r.PathValue("categoryId") + + pathParamCategoryID, err := runtime.ParseString[int](pathParamCategoryIDStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "GetCategory", + Message: err.Error(), + ParamName: "categoryId", + ParamLocation: "path", + }) + return + } + pathParams.CategoryID = pathParamCategoryID + opts.PathParams = pathParams + + // Parse header parameters + headerParams := &GetCategoryHeaders{} + headers := r.Header + if headerValues := headers[http.CanonicalHeaderKey("X-Include-Products")]; len(headerValues) > 0 { + headerParamXIncludeProducts, err := runtime.ParseString[bool](headerValues[0]) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "GetCategory", + Message: err.Error(), + ParamName: "X-Include-Products", + ParamLocation: "header", + }) + return + } + headerParams.XIncludeProducts = &headerParamXIncludeProducts + } + if headerValues := headers[http.CanonicalHeaderKey("X-Max-Depth")]; len(headerValues) > 0 { + headerParamXMaxDepth, err := runtime.ParseString[int](headerValues[0]) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "GetCategory", + Message: err.Error(), + ParamName: "X-Max-Depth", + ParamLocation: "header", + }) + return + } + headerParams.XMaxDepth = &headerParamXMaxDepth + } + if headerValues := headers[http.CanonicalHeaderKey("X-Price-Threshold")]; len(headerValues) > 0 { + headerParamXPriceThreshold, err := runtime.ParseString[float32](headerValues[0]) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "GetCategory", + Message: err.Error(), + ParamName: "X-Price-Threshold", + ParamLocation: "header", + }) + return + } + headerParams.XPriceThreshold = &headerParamXPriceThreshold + } + opts.Header = headerParams + + // Call business logic + resp, err := a.svc.GetCategory(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetItemsByStatus handles GET /items/{type}/{rating} +func (a *HTTPAdapter) GetItemsByStatus(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetItemsByStatusServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetItemsByStatusPath{} + pathParamTypeStr := r.PathValue("type") + pathParams.Type = pathParamTypeStr + pathParamRatingStr := r.PathValue("rating") + + pathParamRating, err := runtime.ParseString[float32](pathParamRatingStr) + if err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindParse, + OperationID: "GetItemsByStatus", + Message: err.Error(), + ParamName: "rating", + ParamLocation: "path", + }) + return + } + pathParams.Rating = pathParamRating + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetItemsByStatus(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// GetUserPost handles GET /users/{id}/posts/{postId} +func (a *HTTPAdapter) GetUserPost(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &GetUserPostServiceRequestOptions{} + opts.RawRequest = r + + // Parse path parameters + pathParams := &GetUserPostPath{} + pathParamIDStr := r.PathValue("id") + pathParams.ID = pathParamIDStr + pathParamPostIDStr := r.PathValue("postId") + pathParams.PostID = pathParamPostIDStr + opts.PathParams = pathParams + + // Call business logic + resp, err := a.svc.GetUserPost(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 200 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// CreateOrder handles POST /orders +func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &CreateOrderServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body CreateOrderBody + if err := a.jsonBodyDecoder(r.Body, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "CreateOrder", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.CreateOrder(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// CreateCompany handles POST /companies +func (a *HTTPAdapter) CreateCompany(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + opts := &CreateCompanyServiceRequestOptions{} + opts.RawRequest = r + + // Parse request body + defer r.Body.Close() + var body CreateCompanyBody + if err := a.jsonBodyDecoder(r.Body, &body); err != nil { + a.errHandler.HandleError(w, r, http.StatusBadRequest, OapiHandlerError{ + Kind: OapiErrorKindDecode, + OperationID: "CreateCompany", + Message: err.Error(), + }) + return + } + opts.Body = &body + + // Call business logic + resp, err := a.svc.CreateCompany(ctx, opts) + if err != nil { + code := http.StatusInternalServerError + if resp != nil && resp.Status != 0 { + code = resp.Status + } + a.errHandler.HandleError(w, r, code, err) + return + } + + // Apply custom headers from response + if resp != nil && resp.Headers != nil { + for k, v := range resp.Headers { + for _, val := range v { + w.Header().Add(k, val) + } + } + } + + // Determine status code + status := 201 + if resp != nil && resp.Status != 0 { + status = resp.Status + } + if w.Header().Get("Content-Type") == "" { + w.Header().Set("Content-Type", "application/json") + } + w.WriteHeader(status) + if resp != nil && resp.Body != nil { + _ = json.NewEncoder(w).Encode(resp.Body) + } +} + +// RouterOption is a function that configures the router. +type RouterOption func(*routerConfig) + +type routerConfig struct { + middlewares []echo.MiddlewareFunc + errHandler OapiErrorHandler + jsonBodyDecoder runtime.JSONBodyDecoderFunc +} + +// WithMiddleware adds middleware to the router. +func WithMiddleware(mw echo.MiddlewareFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.middlewares = append(cfg.middlewares, mw) + } +} + +// WithErrorHandler sets a custom error handler for the router. +// If not set, OapiDefaultErrorHandler is used. +func WithErrorHandler(h OapiErrorHandler) RouterOption { + return func(cfg *routerConfig) { + cfg.errHandler = h + } +} + +// WithJSONBodyDecoder sets a custom function for decoding JSON request bodies. +// If not set, runtime.DecodeJSONBody is used. +func WithJSONBodyDecoder(fn runtime.JSONBodyDecoderFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.jsonBodyDecoder = fn + } +} + +// NewRouter registers routes on the given Echo instance with the service implementation. +func NewRouter(e *echo.Echo, svc ServiceInterface, opts ...RouterOption) { + cfg := &routerConfig{} + for _, opt := range opts { + opt(cfg) + } + + // Apply middleware to all routes + for _, mw := range cfg.middlewares { + e.Use(mw) + } + + adapter := NewHTTPAdapter(svc, cfg.errHandler) + if cfg.jsonBodyDecoder != nil { + adapter.jsonBodyDecoder = cfg.jsonBodyDecoder + } + e.GET("/health", func(c *echo.Context) error { + adapter.HealthCheck(c.Response(), c.Request()) + return nil + }) + e.GET("/users", func(c *echo.Context) error { + adapter.ListUsers(c.Response(), c.Request()) + return nil + }) + e.POST("/users", func(c *echo.Context) error { + adapter.CreateUser(c.Response(), c.Request()) + return nil + }) + e.POST("/users/import", func(c *echo.Context) error { + adapter.ImportUsers(c.Response(), c.Request()) + return nil + }) + e.GET("/users/:id", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.GetUser(c.Response(), c.Request()) + return nil + }) + e.DELETE("/users/:id", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.DeleteUser(c.Response(), c.Request()) + return nil + }) + e.GET("/users/:id/avatar", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.GetUserAvatar(c.Response(), c.Request()) + return nil + }) + e.PUT("/users/:id/avatar", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + adapter.UploadUserAvatar(c.Response(), c.Request()) + return nil + }) + e.POST("/contact", func(c *echo.Context) error { + adapter.SubmitContactForm(c.Response(), c.Request()) + return nil + }) + e.POST("/notes", func(c *echo.Context) error { + adapter.CreateNote(c.Response(), c.Request()) + return nil + }) + e.POST("/xml-data", func(c *echo.Context) error { + adapter.ProcessXMLData(c.Response(), c.Request()) + return nil + }) + e.GET("/export", func(c *echo.Context) error { + adapter.ExportData(c.Response(), c.Request()) + return nil + }) + e.POST("/oauth/token", func(c *echo.Context) error { + adapter.GetOAuthToken(c.Response(), c.Request()) + return nil + }) + e.GET("/items/:type", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("type", c.Param("type")) + adapter.GetItemsByType(c.Response(), c.Request()) + return nil + }) + e.GET("/search", func(c *echo.Context) error { + adapter.Search(c.Response(), c.Request()) + return nil + }) + e.GET("/status", func(c *echo.Context) error { + adapter.GetStatus(c.Response(), c.Request()) + return nil + }) + e.POST("/images", func(c *echo.Context) error { + adapter.UploadImage(c.Response(), c.Request()) + return nil + }) + e.GET("/products", func(c *echo.Context) error { + adapter.ListProducts(c.Response(), c.Request()) + return nil + }) + e.GET("/categories/:categoryId", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("categoryId", c.Param("categoryId")) + adapter.GetCategory(c.Response(), c.Request()) + return nil + }) + e.GET("/items/:type/:rating", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("type", c.Param("type")) + c.Request().SetPathValue("rating", c.Param("rating")) + adapter.GetItemsByStatus(c.Response(), c.Request()) + return nil + }) + e.GET("/users/:id/posts/:postId", func(c *echo.Context) error { + // Copy path params to request for http.Handler compatibility + c.Request().SetPathValue("id", c.Param("id")) + c.Request().SetPathValue("postId", c.Param("postId")) + adapter.GetUserPost(c.Response(), c.Request()) + return nil + }) + e.POST("/orders", func(c *echo.Context) error { + adapter.CreateOrder(c.Response(), c.Request()) + return nil + }) + e.POST("/companies", func(c *echo.Context) error { + adapter.CreateCompany(c.Response(), c.Request()) + return nil + }) +} + +// HealthCheckResponseData wraps the success response with optional headers and status override. +type HealthCheckResponseData struct { + Body *HealthCheckResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewHealthCheckResponseData creates a new HealthCheckResponseData with the given body. +func NewHealthCheckResponseData(body *HealthCheckResponse) *HealthCheckResponseData { + return &HealthCheckResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *HealthCheckResponseData) WithHeaders(h http.Header) *HealthCheckResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *HealthCheckResponseData) WithStatus(code int) *HealthCheckResponseData { + r.Status = code + return r +} + +// ListUsersResponseData wraps the success response with optional headers and status override. +type ListUsersResponseData struct { + Body *ListUsersResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewListUsersResponseData creates a new ListUsersResponseData with the given body. +func NewListUsersResponseData(body *ListUsersResponse) *ListUsersResponseData { + return &ListUsersResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ListUsersResponseData) WithHeaders(h http.Header) *ListUsersResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ListUsersResponseData) WithStatus(code int) *ListUsersResponseData { + r.Status = code + return r +} + +// CreateUserResponseData wraps the success response with optional headers and status override. +type CreateUserResponseData struct { + Body *CreateUserResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewCreateUserResponseData creates a new CreateUserResponseData with the given body. +func NewCreateUserResponseData(body *CreateUserResponse) *CreateUserResponseData { + return &CreateUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *CreateUserResponseData) WithHeaders(h http.Header) *CreateUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *CreateUserResponseData) WithStatus(code int) *CreateUserResponseData { + r.Status = code + return r +} + +// ImportUsersResponseData wraps the success response with optional headers and status override. +type ImportUsersResponseData struct { + Body *ImportUsersResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewImportUsersResponseData creates a new ImportUsersResponseData with the given body. +func NewImportUsersResponseData(body *ImportUsersResponse) *ImportUsersResponseData { + return &ImportUsersResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ImportUsersResponseData) WithHeaders(h http.Header) *ImportUsersResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ImportUsersResponseData) WithStatus(code int) *ImportUsersResponseData { + r.Status = code + return r +} + +// GetUserResponseData wraps the success response with optional headers and status override. +type GetUserResponseData struct { + Body *GetUserResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetUserResponseData creates a new GetUserResponseData with the given body. +func NewGetUserResponseData(body *GetUserResponse) *GetUserResponseData { + return &GetUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetUserResponseData) WithHeaders(h http.Header) *GetUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetUserResponseData) WithStatus(code int) *GetUserResponseData { + r.Status = code + return r +} + +// DeleteUserResponseData wraps the success response with optional headers and status override. +type DeleteUserResponseData struct { + Body *struct{} + Headers http.Header + Status int // 0 = use default (204) +} + +// NewDeleteUserResponseData creates a new DeleteUserResponseData with the given body. +func NewDeleteUserResponseData(body *struct{}) *DeleteUserResponseData { + return &DeleteUserResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *DeleteUserResponseData) WithHeaders(h http.Header) *DeleteUserResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *DeleteUserResponseData) WithStatus(code int) *DeleteUserResponseData { + r.Status = code + return r +} + +// GetUserAvatarResponseData wraps the success response with optional headers and status override. +type GetUserAvatarResponseData struct { + Body *GetUserAvatarResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetUserAvatarResponseData creates a new GetUserAvatarResponseData with the given body. +func NewGetUserAvatarResponseData(body *GetUserAvatarResponse) *GetUserAvatarResponseData { + return &GetUserAvatarResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetUserAvatarResponseData) WithHeaders(h http.Header) *GetUserAvatarResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetUserAvatarResponseData) WithStatus(code int) *GetUserAvatarResponseData { + r.Status = code + return r +} + +// UploadUserAvatarResponseData wraps the success response with optional headers and status override. +type UploadUserAvatarResponseData struct { + Body *struct{} + Headers http.Header + Status int // 0 = use default (204) +} + +// NewUploadUserAvatarResponseData creates a new UploadUserAvatarResponseData with the given body. +func NewUploadUserAvatarResponseData(body *struct{}) *UploadUserAvatarResponseData { + return &UploadUserAvatarResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *UploadUserAvatarResponseData) WithHeaders(h http.Header) *UploadUserAvatarResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *UploadUserAvatarResponseData) WithStatus(code int) *UploadUserAvatarResponseData { + r.Status = code + return r +} + +// SubmitContactFormResponseData wraps the success response with optional headers and status override. +type SubmitContactFormResponseData struct { + Body *SubmitContactFormResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewSubmitContactFormResponseData creates a new SubmitContactFormResponseData with the given body. +func NewSubmitContactFormResponseData(body *SubmitContactFormResponse) *SubmitContactFormResponseData { + return &SubmitContactFormResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *SubmitContactFormResponseData) WithHeaders(h http.Header) *SubmitContactFormResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *SubmitContactFormResponseData) WithStatus(code int) *SubmitContactFormResponseData { + r.Status = code + return r +} + +// CreateNoteResponseData wraps the success response with optional headers and status override. +type CreateNoteResponseData struct { + Body *CreateNoteResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewCreateNoteResponseData creates a new CreateNoteResponseData with the given body. +func NewCreateNoteResponseData(body *CreateNoteResponse) *CreateNoteResponseData { + return &CreateNoteResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *CreateNoteResponseData) WithHeaders(h http.Header) *CreateNoteResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *CreateNoteResponseData) WithStatus(code int) *CreateNoteResponseData { + r.Status = code + return r +} + +// ProcessXMLDataResponseData wraps the success response with optional headers and status override. +type ProcessXMLDataResponseData struct { + Body []byte + Headers http.Header + Status int // 0 = use default (200) +} + +// NewProcessXMLDataResponseData creates a new ProcessXMLDataResponseData with the given body. +func NewProcessXMLDataResponseData(body []byte) *ProcessXMLDataResponseData { + return &ProcessXMLDataResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ProcessXMLDataResponseData) WithHeaders(h http.Header) *ProcessXMLDataResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ProcessXMLDataResponseData) WithStatus(code int) *ProcessXMLDataResponseData { + r.Status = code + return r +} + +// ExportDataResponseData wraps the success response with optional headers and status override. +type ExportDataResponseData struct { + Body *ExportDataResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewExportDataResponseData creates a new ExportDataResponseData with the given body. +func NewExportDataResponseData(body *ExportDataResponse) *ExportDataResponseData { + return &ExportDataResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ExportDataResponseData) WithHeaders(h http.Header) *ExportDataResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ExportDataResponseData) WithStatus(code int) *ExportDataResponseData { + r.Status = code + return r +} + +// GetOAuthTokenResponseData wraps the success response with optional headers and status override. +type GetOAuthTokenResponseData struct { + Body *GetOAuthTokenResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetOAuthTokenResponseData creates a new GetOAuthTokenResponseData with the given body. +func NewGetOAuthTokenResponseData(body *GetOAuthTokenResponse) *GetOAuthTokenResponseData { + return &GetOAuthTokenResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetOAuthTokenResponseData) WithHeaders(h http.Header) *GetOAuthTokenResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetOAuthTokenResponseData) WithStatus(code int) *GetOAuthTokenResponseData { + r.Status = code + return r +} + +// GetItemsByTypeResponseData wraps the success response with optional headers and status override. +type GetItemsByTypeResponseData struct { + Body *GetItemsByTypeResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetItemsByTypeResponseData creates a new GetItemsByTypeResponseData with the given body. +func NewGetItemsByTypeResponseData(body *GetItemsByTypeResponse) *GetItemsByTypeResponseData { + return &GetItemsByTypeResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetItemsByTypeResponseData) WithHeaders(h http.Header) *GetItemsByTypeResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetItemsByTypeResponseData) WithStatus(code int) *GetItemsByTypeResponseData { + r.Status = code + return r +} + +// SearchResponseData wraps the success response with optional headers and status override. +type SearchResponseData struct { + Body *SearchResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewSearchResponseData creates a new SearchResponseData with the given body. +func NewSearchResponseData(body *SearchResponse) *SearchResponseData { + return &SearchResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *SearchResponseData) WithHeaders(h http.Header) *SearchResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *SearchResponseData) WithStatus(code int) *SearchResponseData { + r.Status = code + return r +} + +// GetStatusResponseData wraps the success response with optional headers and status override. +type GetStatusResponseData struct { + Body *GetStatusResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetStatusResponseData creates a new GetStatusResponseData with the given body. +func NewGetStatusResponseData(body *GetStatusResponse) *GetStatusResponseData { + return &GetStatusResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetStatusResponseData) WithHeaders(h http.Header) *GetStatusResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetStatusResponseData) WithStatus(code int) *GetStatusResponseData { + r.Status = code + return r +} + +// UploadImageResponseData wraps the success response with optional headers and status override. +type UploadImageResponseData struct { + Body *UploadImageResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewUploadImageResponseData creates a new UploadImageResponseData with the given body. +func NewUploadImageResponseData(body *UploadImageResponse) *UploadImageResponseData { + return &UploadImageResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *UploadImageResponseData) WithHeaders(h http.Header) *UploadImageResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *UploadImageResponseData) WithStatus(code int) *UploadImageResponseData { + r.Status = code + return r +} + +// ListProductsResponseData wraps the success response with optional headers and status override. +type ListProductsResponseData struct { + Body *ListProductsResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewListProductsResponseData creates a new ListProductsResponseData with the given body. +func NewListProductsResponseData(body *ListProductsResponse) *ListProductsResponseData { + return &ListProductsResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *ListProductsResponseData) WithHeaders(h http.Header) *ListProductsResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *ListProductsResponseData) WithStatus(code int) *ListProductsResponseData { + r.Status = code + return r +} + +// GetCategoryResponseData wraps the success response with optional headers and status override. +type GetCategoryResponseData struct { + Body *GetCategoryResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetCategoryResponseData creates a new GetCategoryResponseData with the given body. +func NewGetCategoryResponseData(body *GetCategoryResponse) *GetCategoryResponseData { + return &GetCategoryResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetCategoryResponseData) WithHeaders(h http.Header) *GetCategoryResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetCategoryResponseData) WithStatus(code int) *GetCategoryResponseData { + r.Status = code + return r +} + +// GetItemsByStatusResponseData wraps the success response with optional headers and status override. +type GetItemsByStatusResponseData struct { + Body *GetItemsByStatusResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetItemsByStatusResponseData creates a new GetItemsByStatusResponseData with the given body. +func NewGetItemsByStatusResponseData(body *GetItemsByStatusResponse) *GetItemsByStatusResponseData { + return &GetItemsByStatusResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetItemsByStatusResponseData) WithHeaders(h http.Header) *GetItemsByStatusResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetItemsByStatusResponseData) WithStatus(code int) *GetItemsByStatusResponseData { + r.Status = code + return r +} + +// GetUserPostResponseData wraps the success response with optional headers and status override. +type GetUserPostResponseData struct { + Body *GetUserPostResponse + Headers http.Header + Status int // 0 = use default (200) +} + +// NewGetUserPostResponseData creates a new GetUserPostResponseData with the given body. +func NewGetUserPostResponseData(body *GetUserPostResponse) *GetUserPostResponseData { + return &GetUserPostResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *GetUserPostResponseData) WithHeaders(h http.Header) *GetUserPostResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *GetUserPostResponseData) WithStatus(code int) *GetUserPostResponseData { + r.Status = code + return r +} + +// CreateOrderResponseData wraps the success response with optional headers and status override. +type CreateOrderResponseData struct { + Body *CreateOrderResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewCreateOrderResponseData creates a new CreateOrderResponseData with the given body. +func NewCreateOrderResponseData(body *CreateOrderResponse) *CreateOrderResponseData { + return &CreateOrderResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *CreateOrderResponseData) WithHeaders(h http.Header) *CreateOrderResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *CreateOrderResponseData) WithStatus(code int) *CreateOrderResponseData { + r.Status = code + return r +} + +// CreateCompanyResponseData wraps the success response with optional headers and status override. +type CreateCompanyResponseData struct { + Body *CreateCompanyResponse + Headers http.Header + Status int // 0 = use default (201) +} + +// NewCreateCompanyResponseData creates a new CreateCompanyResponseData with the given body. +func NewCreateCompanyResponseData(body *CreateCompanyResponse) *CreateCompanyResponseData { + return &CreateCompanyResponseData{Body: body} +} + +// WithHeaders sets custom headers on the response. +func (r *CreateCompanyResponseData) WithHeaders(h http.Header) *CreateCompanyResponseData { + r.Headers = h + return r +} + +// WithStatus overrides the default status code. +func (r *CreateCompanyResponseData) WithStatus(code int) *CreateCompanyResponseData { + r.Status = code + return r +} + +// ListUsersServiceRequestOptions holds all parameters for the ListUsers operation. +type ListUsersServiceRequestOptions struct { + Query *ListUsersQuery + Header *ListUsersHeaders + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *ListUsersServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// CreateUserServiceRequestOptions holds all parameters for the CreateUser operation. +type CreateUserServiceRequestOptions struct { + Body *CreateUserBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *CreateUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// ImportUsersServiceRequestOptions holds all parameters for the ImportUsers operation. +type ImportUsersServiceRequestOptions struct { + Body *ImportUsersBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *ImportUsersServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetUserServiceRequestOptions holds all parameters for the GetUser operation. +type GetUserServiceRequestOptions struct { + PathParams *GetUserPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// DeleteUserServiceRequestOptions holds all parameters for the DeleteUser operation. +type DeleteUserServiceRequestOptions struct { + PathParams *DeleteUserPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *DeleteUserServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetUserAvatarServiceRequestOptions holds all parameters for the GetUserAvatar operation. +type GetUserAvatarServiceRequestOptions struct { + PathParams *GetUserAvatarPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetUserAvatarServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// UploadUserAvatarServiceRequestOptions holds all parameters for the UploadUserAvatar operation. +type UploadUserAvatarServiceRequestOptions struct { + PathParams *UploadUserAvatarPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *UploadUserAvatarServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// SubmitContactFormServiceRequestOptions holds all parameters for the SubmitContactForm operation. +type SubmitContactFormServiceRequestOptions struct { + Body *SubmitContactFormBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *SubmitContactFormServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// CreateNoteServiceRequestOptions holds all parameters for the CreateNote operation. +type CreateNoteServiceRequestOptions struct { + Body *CreateNoteBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *CreateNoteServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// ProcessXMLDataServiceRequestOptions holds all parameters for the ProcessXMLData operation. +type ProcessXMLDataServiceRequestOptions struct { + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *ProcessXMLDataServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetOAuthTokenServiceRequestOptions holds all parameters for the GetOAuthToken operation. +type GetOAuthTokenServiceRequestOptions struct { + Body *GetOAuthTokenBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetOAuthTokenServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetItemsByTypeServiceRequestOptions holds all parameters for the GetItemsByType operation. +type GetItemsByTypeServiceRequestOptions struct { + PathParams *GetItemsByTypePath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetItemsByTypeServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// SearchServiceRequestOptions holds all parameters for the Search operation. +type SearchServiceRequestOptions struct { + Query *SearchQuery + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *SearchServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// UploadImageServiceRequestOptions holds all parameters for the UploadImage operation. +type UploadImageServiceRequestOptions struct { + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *UploadImageServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if len(errors) == 0 { + return nil + } + + return errors +} + +// ListProductsServiceRequestOptions holds all parameters for the ListProducts operation. +type ListProductsServiceRequestOptions struct { + Query *ListProductsQuery + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *ListProductsServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetCategoryServiceRequestOptions holds all parameters for the GetCategory operation. +type GetCategoryServiceRequestOptions struct { + PathParams *GetCategoryPath + Header *GetCategoryHeaders + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetCategoryServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetItemsByStatusServiceRequestOptions holds all parameters for the GetItemsByStatus operation. +type GetItemsByStatusServiceRequestOptions struct { + PathParams *GetItemsByStatusPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetItemsByStatusServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetUserPostServiceRequestOptions holds all parameters for the GetUserPost operation. +type GetUserPostServiceRequestOptions struct { + PathParams *GetUserPostPath + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *GetUserPostServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// CreateOrderServiceRequestOptions holds all parameters for the CreateOrder operation. +type CreateOrderServiceRequestOptions struct { + Body *CreateOrderBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *CreateOrderServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// CreateCompanyServiceRequestOptions holds all parameters for the CreateCompany operation. +type CreateCompanyServiceRequestOptions struct { + Body *CreateCompanyBody + // RawRequest provides access to the underlying HTTP request for custom content type handling. + RawRequest *http.Request +} + +// Validate validates all the fields in the options. +func (o *CreateCompanyServiceRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} diff --git a/examples/server/test/echo-v5/testcase/cfg.yml b/examples/server/test/echo-v5/testcase/cfg.yml new file mode 100644 index 00000000..1416a640 --- /dev/null +++ b/examples/server/test/echo-v5/testcase/cfg.yml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../../../../../configuration-schema.json +package: testcase +output: + use-single-file: true + filename: adapter.gen.go +generate: + models: false + handler: + kind: echo-v5 + service: {} diff --git a/examples/server/test/echo-v5/testcase/gen.go b/examples/server/test/echo-v5/testcase/gen.go new file mode 100644 index 00000000..8d8a1d7b --- /dev/null +++ b/examples/server/test/echo-v5/testcase/gen.go @@ -0,0 +1,624 @@ +// Code generated by oapi-codegen. DO NOT EDIT. + +package testcase + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" + "github.com/go-playground/validator/v10" +) + +type OrderStatus string + +const ( + Confirmed OrderStatus = "confirmed" + Delivered OrderStatus = "delivered" + Pending OrderStatus = "pending" + Shipped OrderStatus = "shipped" +) + +// Validate checks if the OrderStatus value is valid +func (o OrderStatus) Validate() error { + switch o { + case Confirmed, Delivered, Pending, Shipped: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid OrderStatus value, got: %v", o)) + } +} + +type ListUsersHeaders struct { + // XRequestID Unique request identifier for tracing + XRequestID string `json:"X-Request-ID" validate:"required"` +} + +func (l ListUsersHeaders) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(l)) +} + +type GetCategoryHeaders struct { + // XIncludeProducts Include products count (boolean header) + XIncludeProducts *bool `json:"X-Include-Products,omitempty"` + + // XMaxDepth Max depth for nested categories (integer header) + XMaxDepth *int `json:"X-Max-Depth,omitempty"` + + // XPriceThreshold Price threshold filter (number header) + XPriceThreshold *float32 `json:"X-Price-Threshold,omitempty"` +} + +type GetUserPath struct { + ID string `json:"id" validate:"required"` +} + +func (g GetUserPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type DeleteUserPath struct { + ID string `json:"id" validate:"required"` +} + +func (d DeleteUserPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(d)) +} + +type GetUserAvatarPath struct { + ID string `json:"id" validate:"required"` +} + +func (g GetUserAvatarPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type UploadUserAvatarPath struct { + ID string `json:"id" validate:"required"` +} + +func (u UploadUserAvatarPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(u)) +} + +type GetItemsByTypePath struct { + Type string `json:"type" validate:"required"` +} + +func (g GetItemsByTypePath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GetCategoryPath struct { + CategoryID int `json:"categoryId"` +} + +type GetItemsByStatusPath struct { + Type string `json:"type" validate:"required"` + Rating float32 `json:"rating"` +} + +func (g GetItemsByStatusPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GetUserPostPath struct { + ID string `json:"id" validate:"required"` + PostID string `json:"postId" validate:"required"` +} + +func (g GetUserPostPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type CreateUserBody = CreateUserRequest + +type ImportUsersBody struct { + // File CSV file with user data + File runtime.File `json:"file" validate:"required"` + + // Overwrite Overwrite existing users + Overwrite *bool `json:"overwrite,omitempty"` +} + +func (i ImportUsersBody) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(i.File).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("File", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type UploadUserAvatarBody = runtime.File + +type SubmitContactFormBody struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` + Message string `json:"message" validate:"required"` +} + +func (s SubmitContactFormBody) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type CreateNoteBody = string + +type ProcessXMLDataBody = XMLPayload + +type GetOAuthTokenBody struct { + GrantType string `json:"grant_type" validate:"required"` + ClientID string `json:"client_id" validate:"required"` + ClientSecret *string `json:"client_secret,omitempty"` +} + +func (g GetOAuthTokenBody) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type UploadImageBody = runtime.File + +type CreateOrderBody = CreateOrderRequest + +type CreateCompanyBody = CreateCompanyRequest + +type ListUsersQuery struct { + Limit *int `json:"limit,omitempty"` +} + +type SearchQuery struct { + Q string `json:"q" validate:"required"` +} + +func (s SearchQuery) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type ListProductsQuery struct { + // Ids Filter by product IDs (string array) + Ids []string `json:"ids,omitempty"` + + // Tags Filter by tags (string array) + Tags []string `json:"tags,omitempty"` + + // CategoryIds Filter by category IDs (integer array) + CategoryIds []int `json:"categoryIds,omitempty"` + + // MinPrice Minimum price (number/float) + MinPrice *float32 `json:"minPrice,omitempty"` + + // Active Filter by active status (boolean) + Active *bool `json:"active,omitempty"` +} + +type StatusResponse struct { + Status *string `json:"status,omitempty"` + Uptime *int `json:"uptime,omitempty"` +} + +type HealthCheckResponse = string + +type ListUsersResponse []User + +type CreateUserResponse = User + +type CreateUserErrorResponse = Error + +type ImportUsersResponse = ImportResult + +type GetUserResponse = User + +type GetUserErrorResponse = Error + +type GetUserAvatarResponse = runtime.File + +type GetUserAvatarErrorResponse string + +func (r GetUserAvatarErrorResponse) Error() string { + return "unmapped client error" +} + +type SubmitContactFormResponse map[string]any + +type CreateNoteResponse = int + +type ProcessXMLDataResponse = []byte + +type ExportDataResponse = runtime.File + +type GetOAuthTokenResponse = TokenResponse + +type GetItemsByTypeResponse []string + +type SearchResponse struct { + Search_Response_OneOf *Search_Response_OneOf `json:"-"` +} + +func (s SearchResponse) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.Search_Response_OneOf) + if err != nil { + return nil, fmt.Errorf("Search_Response_OneOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchResponse) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.Search_Response_OneOf == nil { + s.Search_Response_OneOf = &Search_Response_OneOf{} + } + + if err := runtime.UnmarshalJSON(data, s.Search_Response_OneOf); err != nil { + return fmt.Errorf("Search_Response_OneOf unmarshal: %w", err) + } + + return nil +} + +type GetStatusResponse = StatusResponse + +type UploadImageResponse struct { + ID *string `json:"id,omitempty"` + URL *string `json:"url,omitempty"` +} + +type ListProductsResponse []Product + +type GetCategoryResponse = Category + +type GetItemsByStatusResponse []string + +type GetUserPostResponse = Post + +type GetUserPostErrorResponse = NotFoundError + +type CreateOrderResponse = Order + +type CreateOrderErrorResponse = ValidationError + +type CreateOrderErrorResponseJSON = ConflictError + +type CreateCompanyResponse = Company + +type SearchItem struct { + ID string `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description *string `json:"description,omitempty"` +} + +func (s SearchItem) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type User struct { + ID string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` +} + +func (u User) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(u)) +} + +type CreateUserRequest struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` +} + +func (c CreateUserRequest) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type ImportResult struct { + Imported *int `json:"imported,omitempty"` + Skipped *int `json:"skipped,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +type Error struct { + Code *string `json:"code,omitempty"` + Message *string `json:"message,omitempty"` +} + +func (s Error) Error() string { + return "unmapped client error" +} + +type TokenResponse struct { + AccessToken string `json:"access_token" validate:"required"` + TokenType string `json:"token_type" validate:"required"` + ExpiresIn *int `json:"expires_in,omitempty"` +} + +func (t TokenResponse) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + +type XMLPayload struct { + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` +} + +type Category struct { + ID int `json:"id"` + Name string `json:"name" validate:"required"` + Description *string `json:"description,omitempty"` +} + +func (c Category) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type Product struct { + ID string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Price float32 `json:"price"` + Tags []string `json:"tags,omitempty"` +} + +func (p Product) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(p)) +} + +type Post struct { + ID string `json:"id" validate:"required"` + UserID string `json:"userId" validate:"required"` + Title string `json:"title" validate:"required"` + Content string `json:"content" validate:"required"` + CreatedAt *time.Time `json:"createdAt,omitempty"` +} + +func (p Post) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(p)) +} + +type NotFoundError struct { + Code string `json:"code" validate:"required"` + Message string `json:"message" validate:"required"` + + // Resource The resource type that was not found + Resource string `json:"resource" validate:"required"` +} + +func (n NotFoundError) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(n)) +} + +func (s NotFoundError) Error() string { + return "unmapped client error" +} + +type ValidationError struct { + Code string `json:"code" validate:"required"` + Message string `json:"message" validate:"required"` + Fields ValidationError_Fields `json:"fields" validate:"required"` +} + +func (v ValidationError) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(v.Code, "required"); err != nil { + errors = errors.Append("Code", err) + } + if err := typesValidator.Var(v.Message, "required"); err != nil { + errors = errors.Append("Message", err) + } + if v, ok := any(v.Fields).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Fields", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +func (s ValidationError) Error() string { + return "unmapped client error" +} + +type ValidationError_Fields []ValidationError_Fields_Item + +func (v ValidationError_Fields) Validate() error { + var errors runtime.ValidationErrors + for i, item := range v { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ValidationError_Fields_Item struct { + Field *string `json:"field,omitempty"` + ErrorData *string `json:"error,omitempty"` +} + +type ConflictError struct { + Code string `json:"code" validate:"required"` + Message string `json:"message" validate:"required"` + + // ExistingID ID of the conflicting resource + ExistingID *string `json:"existingId,omitempty"` +} + +func (c ConflictError) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type CreateOrderRequest struct { + ProductID string `json:"productId" validate:"required"` + Quantity int `json:"quantity" validate:"gte=1"` + Notes *string `json:"notes,omitempty"` +} + +func (c CreateOrderRequest) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type Order struct { + ID string `json:"id" validate:"required"` + ProductID string `json:"productId" validate:"required"` + Quantity int `json:"quantity"` + Status OrderStatus `json:"status" validate:"required"` + CreatedAt *time.Time `json:"createdAt,omitempty"` +} + +func (o Order) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(o.ID, "required"); err != nil { + errors = errors.Append("ID", err) + } + if err := typesValidator.Var(o.ProductID, "required"); err != nil { + errors = errors.Append("ProductID", err) + } + if v, ok := any(o.Status).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Status", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type Address struct { + Street string `json:"street" validate:"required"` + City string `json:"city" validate:"required"` + State *string `json:"state,omitempty"` + PostalCode *string `json:"postalCode,omitempty"` + Country string `json:"country" validate:"required"` +} + +func (a Address) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(a)) +} + +type CreateCompanyRequest struct { + Name string `json:"name" validate:"required"` + Address Address `json:"address"` + Contacts *CreateCompanyRequest_Contacts `json:"contacts,omitempty"` +} + +func (c CreateCompanyRequest) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(c.Name, "required"); err != nil { + errors = errors.Append("Name", err) + } + if v, ok := any(c.Address).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Address", err) + } + } + if c.Contacts != nil { + if v, ok := any(c.Contacts).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Contacts", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type CreateCompanyRequest_Contacts []CreateCompanyRequest_Contacts_Item + +type CreateCompanyRequest_Contacts_Item struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Phone *string `json:"phone,omitempty"` +} + +type Company struct { + ID string `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Address Address `json:"address"` + Contacts *Company_Contacts `json:"contacts,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty"` +} + +func (c Company) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(c.ID, "required"); err != nil { + errors = errors.Append("ID", err) + } + if err := typesValidator.Var(c.Name, "required"); err != nil { + errors = errors.Append("Name", err) + } + if v, ok := any(c.Address).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Address", err) + } + } + if c.Contacts != nil { + if v, ok := any(c.Contacts).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Contacts", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type Company_Contacts []Company_Contacts_Item + +type Company_Contacts_Item struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Phone *string `json:"phone,omitempty"` +} + +type Search_Response_OneOf struct { + runtime.Either[User, SearchItem] +} + +func (s *Search_Response_OneOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +var typesValidator *validator.Validate + +func init() { + typesValidator = validator.New(validator.WithRequiredStructEnabled()) + runtime.RegisterCustomTypeFunc(typesValidator) +} diff --git a/examples/server/test/echo-v5/testcase/generate.go b/examples/server/test/echo-v5/testcase/generate.go new file mode 100644 index 00000000..c5d0fe88 --- /dev/null +++ b/examples/server/test/echo-v5/testcase/generate.go @@ -0,0 +1,5 @@ +package testcase + +//go:generate cp ../../testcase/gen.go ./gen.go +//go:generate cp ../../testcase/service.go.src ./service.go +//go:generate go run github.com/doordash-oss/oapi-codegen-dd/v3/cmd/oapi-codegen -config cfg.yml ../../api.yml diff --git a/examples/server/test/echo-v5/testcase/service.go b/examples/server/test/echo-v5/testcase/service.go new file mode 100644 index 00000000..5c41c7ad --- /dev/null +++ b/examples/server/test/echo-v5/testcase/service.go @@ -0,0 +1,197 @@ +// Package testcase implements test service logic for all frameworks. +// This file is hand-written and copied to each framework's testcase package. +package testcase + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" +) + +// Service implements the ServiceInterface with test logic. +type Service struct { + avatars map[string][]byte +} + +// NewService creates a new Service. +func NewService() *Service { + return &Service{avatars: make(map[string][]byte)} +} + +// Ensure Service implements ServiceInterface. +var _ ServiceInterface = (*Service)(nil) + +func ptr[T any](v T) *T { return &v } + +// HealthCheck handles GET /health +func (s *Service) HealthCheck(ctx context.Context) (*HealthCheckResponseData, error) { + resp := HealthCheckResponse("OK") + return NewHealthCheckResponseData(&resp), nil +} + +// ListUsers handles GET /users +func (s *Service) ListUsers(ctx context.Context, opts *ListUsersServiceRequestOptions) (*ListUsersResponseData, error) { + users := ListUsersResponse{ + {ID: "1", Name: "Alice", Email: "alice@example.com"}, + {ID: "2", Name: "Bob", Email: "bob@example.com"}, + {ID: "3", Name: "Charlie", Email: "charlie@example.com"}, + } + resp := NewListUsersResponseData(&users) + resp.Headers = http.Header{} + resp.Headers.Set("X-Total-Count", "3") + resp.Headers.Set("X-Page-Token", "next-page-token") + return resp, nil +} + +// CreateUser handles POST /users +func (s *Service) CreateUser(ctx context.Context, opts *CreateUserServiceRequestOptions) (*CreateUserResponseData, error) { + user := User{ID: "new-user-id", Name: opts.Body.Name, Email: opts.Body.Email} + resp := NewCreateUserResponseData(&user) + resp.Status = http.StatusCreated + return resp, nil +} + +// ImportUsers handles POST /users/import +func (s *Service) ImportUsers(ctx context.Context, opts *ImportUsersServiceRequestOptions) (*ImportUsersResponseData, error) { + result := ImportResult{Imported: ptr(5), Skipped: ptr(0)} + return NewImportUsersResponseData(&result), nil +} + +// GetUser handles GET /users/{id} +func (s *Service) GetUser(ctx context.Context, opts *GetUserServiceRequestOptions) (*GetUserResponseData, error) { + user := User{ID: opts.PathParams.ID, Name: "Test User", Email: "test@example.com"} + return NewGetUserResponseData(&user), nil +} + +// DeleteUser handles DELETE /users/{id} +func (s *Service) DeleteUser(ctx context.Context, opts *DeleteUserServiceRequestOptions) (*DeleteUserResponseData, error) { + resp := NewDeleteUserResponseData(nil) + resp.Status = http.StatusNoContent + return resp, nil +} + +// GetUserAvatar handles GET /users/{id}/avatar +func (s *Service) GetUserAvatar(ctx context.Context, opts *GetUserAvatarServiceRequestOptions) (*GetUserAvatarResponseData, error) { + if data, ok := s.avatars[opts.PathParams.ID]; ok { + var file runtime.File + file.InitFromBytes(data, "avatar") + return NewGetUserAvatarResponseData(&file), nil + } + return nil, fmt.Errorf("avatar not found") +} + +// UploadUserAvatar handles PUT /users/{id}/avatar +func (s *Service) UploadUserAvatar(ctx context.Context, opts *UploadUserAvatarServiceRequestOptions) (*UploadUserAvatarResponseData, error) { + body, _ := io.ReadAll(opts.RawRequest.Body) + s.avatars[opts.PathParams.ID] = body + resp := NewUploadUserAvatarResponseData(nil) + resp.Status = http.StatusNoContent + return resp, nil +} + +// SubmitContactForm handles POST /contact +func (s *Service) SubmitContactForm(ctx context.Context, opts *SubmitContactFormServiceRequestOptions) (*SubmitContactFormResponseData, error) { + result := SubmitContactFormResponse{"success": true} + return NewSubmitContactFormResponseData(&result), nil +} + +// CreateNote handles POST /notes +func (s *Service) CreateNote(ctx context.Context, opts *CreateNoteServiceRequestOptions) (*CreateNoteResponseData, error) { + noteID := CreateNoteResponse(1) // Return note ID as integer + resp := NewCreateNoteResponseData(¬eID) + resp.Status = http.StatusCreated + return resp, nil +} + +// ProcessXMLData handles POST /xml-data +func (s *Service) ProcessXMLData(ctx context.Context, opts *ProcessXMLDataServiceRequestOptions) (*ProcessXMLDataResponseData, error) { + return NewProcessXMLDataResponseData([]byte("OK")), nil +} + +// ExportData handles GET /export +func (s *Service) ExportData(ctx context.Context) (*ExportDataResponseData, error) { + var data runtime.File + data.InitFromBytes([]byte("export-data"), "export.bin") + return NewExportDataResponseData(&data), nil +} + +// GetOAuthToken handles POST /oauth/token +func (s *Service) GetOAuthToken(ctx context.Context, opts *GetOAuthTokenServiceRequestOptions) (*GetOAuthTokenResponseData, error) { + token := TokenResponse{AccessToken: "test-token", TokenType: "Bearer", ExpiresIn: ptr(3600)} + return NewGetOAuthTokenResponseData(&token), nil +} + +// GetItemsByType handles GET /items/{type} +func (s *Service) GetItemsByType(ctx context.Context, opts *GetItemsByTypeServiceRequestOptions) (*GetItemsByTypeResponseData, error) { + items := GetItemsByTypeResponse{fmt.Sprintf("item-%s-1", opts.PathParams.Type)} + return NewGetItemsByTypeResponseData(&items), nil +} + +// Search handles GET /search +func (s *Service) Search(ctx context.Context, opts *SearchServiceRequestOptions) (*SearchResponseData, error) { + q := opts.Query.Q + var result SearchResponse + if strings.HasPrefix(q, "user:") { + name := strings.TrimPrefix(q, "user:") + user := User{ID: "user-1", Name: name, Email: name + "@example.com"} + result.Search_Response_OneOf = &Search_Response_OneOf{runtime.NewEitherFromA[User, SearchItem](user)} + } else { + item := SearchItem{ID: "item-1", Title: q, Description: ptr("Search result")} + result.Search_Response_OneOf = &Search_Response_OneOf{runtime.NewEitherFromB[User, SearchItem](item)} + } + return NewSearchResponseData(&result), nil +} + +// GetStatus handles GET /status +func (s *Service) GetStatus(ctx context.Context) (*GetStatusResponseData, error) { + status := StatusResponse{Status: ptr("healthy"), Uptime: ptr(3600)} + return NewGetStatusResponseData(&status), nil +} + +// UploadImage handles POST /images +func (s *Service) UploadImage(ctx context.Context, opts *UploadImageServiceRequestOptions) (*UploadImageResponseData, error) { + result := UploadImageResponse{ID: ptr("img-123"), URL: ptr("http://example.com/img-123")} + resp := NewUploadImageResponseData(&result) + resp.Status = http.StatusCreated + return resp, nil +} + +// ListProducts handles GET /products +func (s *Service) ListProducts(ctx context.Context, opts *ListProductsServiceRequestOptions) (*ListProductsResponseData, error) { + products := ListProductsResponse{{ID: "prod-1", Name: "Product 1", Price: 9.99}} + return NewListProductsResponseData(&products), nil +} + +// GetCategory handles GET /categories/{categoryId} +func (s *Service) GetCategory(ctx context.Context, opts *GetCategoryServiceRequestOptions) (*GetCategoryResponseData, error) { + category := Category{ID: opts.PathParams.CategoryID, Name: "Test Category"} + return NewGetCategoryResponseData(&category), nil +} + +// GetItemsByStatus handles GET /items/{type}/{rating} +func (s *Service) GetItemsByStatus(ctx context.Context, opts *GetItemsByStatusServiceRequestOptions) (*GetItemsByStatusResponseData, error) { + items := GetItemsByStatusResponse{fmt.Sprintf("type-%s-rating-%v", opts.PathParams.Type, opts.PathParams.Rating)} + return NewGetItemsByStatusResponseData(&items), nil +} + +// GetUserPost handles GET /users/{id}/posts/{postId} +func (s *Service) GetUserPost(ctx context.Context, opts *GetUserPostServiceRequestOptions) (*GetUserPostResponseData, error) { + post := Post{ID: opts.PathParams.PostID, UserID: opts.PathParams.ID, Title: "Test Post", Content: "Post content"} + return NewGetUserPostResponseData(&post), nil +} + +// CreateOrder handles POST /orders +func (s *Service) CreateOrder(ctx context.Context, opts *CreateOrderServiceRequestOptions) (*CreateOrderResponseData, error) { + order := Order{ID: "order-1", Status: Pending} + return NewCreateOrderResponseData(&order), nil +} + +// CreateCompany handles POST /companies +func (s *Service) CreateCompany(ctx context.Context, opts *CreateCompanyServiceRequestOptions) (*CreateCompanyResponseData, error) { + company := Company{ID: "company-1", Name: opts.Body.Name, Address: opts.Body.Address} + return NewCreateCompanyResponseData(&company), nil +} diff --git a/examples/server/test/server_test.go b/examples/server/test/server_test.go index da677263..597ef4dc 100644 --- a/examples/server/test/server_test.go +++ b/examples/server/test/server_test.go @@ -13,6 +13,7 @@ import ( "github.com/beego/beego/v2/server/web" beegoapi "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/beego/testcase" chiapi "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/chi/testcase" + echov5api "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/echo-v5/testcase" echoapi "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/echo/testcase" fasthttpapi "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/fasthttp/testcase" fiberapi "github.com/doordash-oss/oapi-codegen-dd/v3/examples/server/test/fiber/testcase" @@ -29,6 +30,7 @@ import ( "github.com/gofiber/fiber/v3" iris "github.com/kataras/iris/v12" "github.com/labstack/echo/v4" + echov5 "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" @@ -134,6 +136,11 @@ func testServers() []serverTestCase { echoapi.NewRouter(e, echoapi.NewService()) return e }()}}, + {"echo-v5", httpHandler{func() http.Handler { + e := echov5.New() + echov5api.NewRouter(e, echov5api.NewService()) + return e + }()}}, {"gin", httpHandler{func() http.Handler { gin.SetMode(gin.TestMode) r := gin.New() diff --git a/integration_test.go b/integration_test.go index 37a20518..95686a01 100644 --- a/integration_test.go +++ b/integration_test.go @@ -88,6 +88,7 @@ var allFrameworks = []codegen.HandlerKind{ codegen.HandlerKindBeego, codegen.HandlerKindChi, codegen.HandlerKindEcho, + codegen.HandlerKindEchoV5, codegen.HandlerKindFastHTTP, codegen.HandlerKindFiber, codegen.HandlerKindGin, @@ -723,6 +724,11 @@ func routerInitTest(fw codegen.HandlerKind) string { return `package integration import ("testing"; "github.com/labstack/echo/v4") func TestRouterInit(t *testing.T) { NewRouter(echo.New(), nil) } +` + case codegen.HandlerKindEchoV5: + return `package integration +import ("testing"; "github.com/labstack/echo/v5") +func TestRouterInit(t *testing.T) { NewRouter(echo.New(), nil) } ` case codegen.HandlerKindFiber: return `package integration diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index d631b355..c714ffca 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -356,6 +356,7 @@ const ( HandlerKindBeego HandlerKind = "beego" HandlerKindChi HandlerKind = "chi" HandlerKindEcho HandlerKind = "echo" + HandlerKindEchoV5 HandlerKind = "echo-v5" HandlerKindFastHTTP HandlerKind = "fasthttp" HandlerKindFiber HandlerKind = "fiber" HandlerKindGin HandlerKind = "gin" @@ -371,7 +372,7 @@ const ( // IsValid returns true if the handler kind is a supported value. func (k HandlerKind) IsValid() bool { switch k { - case HandlerKindBeego, HandlerKindChi, HandlerKindEcho, HandlerKindFastHTTP, HandlerKindFiber, HandlerKindGin, HandlerKindGoFrame, HandlerKindGoZero, HandlerKindGorillaMux, HandlerKindHertz, HandlerKindIris, HandlerKindKratos, HandlerKindStdHTTP: + case HandlerKindBeego, HandlerKindChi, HandlerKindEcho, HandlerKindEchoV5, HandlerKindFastHTTP, HandlerKindFiber, HandlerKindGin, HandlerKindGoFrame, HandlerKindGoZero, HandlerKindGorillaMux, HandlerKindHertz, HandlerKindIris, HandlerKindKratos, HandlerKindStdHTTP: return true default: return false diff --git a/pkg/codegen/templates/handler/echo-v5/handler.tmpl b/pkg/codegen/templates/handler/echo-v5/handler.tmpl new file mode 100644 index 00000000..97d3af51 --- /dev/null +++ b/pkg/codegen/templates/handler/echo-v5/handler.tmpl @@ -0,0 +1,99 @@ +{{/* +Copyright 2026 DoorDash, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/}} +{{/* Echo v5 framework template blocks */}} + +{{define "router-import"}}echo "github.com/labstack/echo/v5"{{end}} + +{{/* Echo uses r.PathValue() since we copy params from Echo context to request */}} +{{define "get-path-param"}}r.PathValue("{{ . }}"){{end}} + +{{/* router-path converts OpenAPI path to echo format: {param} -> :param, /** -> /*any */}} +{{define "router-path"}}{{ escapeGoString (replace (replace (replace . "/**" "/*") "{" ":") "}" "") }}{{end}} + +{{define "router-config"}} +type routerConfig struct { + middlewares []echo.MiddlewareFunc + errHandler OapiErrorHandler + jsonBodyDecoder runtime.JSONBodyDecoderFunc +} + +// WithMiddleware adds middleware to the router. +func WithMiddleware(mw echo.MiddlewareFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.middlewares = append(cfg.middlewares, mw) + } +} + +// WithErrorHandler sets a custom error handler for the router. +// If not set, OapiDefaultErrorHandler is used. +func WithErrorHandler(h OapiErrorHandler) RouterOption { + return func(cfg *routerConfig) { + cfg.errHandler = h + } +} + +// WithJSONBodyDecoder sets a custom function for decoding JSON request bodies. +// If not set, runtime.DecodeJSONBody is used. +func WithJSONBodyDecoder(fn runtime.JSONBodyDecoderFunc) RouterOption { + return func(cfg *routerConfig) { + cfg.jsonBodyDecoder = fn + } +} +{{end}} + +{{define "new-router"}} +{{- $config := .Config -}} +{{- $operations := .Operations -}} +{{- $serviceName := $config.Generate.Handler.Name -}} +// NewRouter registers routes on the given Echo instance with the service implementation. +func NewRouter(e *echo.Echo, svc {{ $serviceName }}Interface, opts ...RouterOption) { + cfg := &routerConfig{} + for _, opt := range opts { + opt(cfg) + } + + // Apply middleware to all routes + for _, mw := range cfg.middlewares { + e.Use(mw) + } + + {{- if $operations }} + + adapter := NewHTTPAdapter(svc, cfg.errHandler) + if cfg.jsonBodyDecoder != nil { + adapter.jsonBodyDecoder = cfg.jsonBodyDecoder + } + {{- range $operations }}{{ $op := . }} + e.{{ $op.Method | caps }}("{{template "router-path" $op.Path}}", func(c *echo.Context) error { + {{- if $op.PathParams }} + // Copy path params to request for http.Handler compatibility + {{- range $op.PathParams.Schema.Properties }} + c.Request().SetPathValue("{{ .JsonFieldName }}", c.Param("{{ .JsonFieldName }}")) + {{- end }} + {{- end }} + adapter.{{ $op.ID | ucFirst }}(c.Response(), c.Request()) + return nil + }) + {{- end }} + {{- else }} + _ = svc // unused when no operations + {{- end }} +} +{{end}} + +{{template "handler/errors.tmpl" .}} +{{template "handler/adapter.tmpl" .}} +{{template "handler/router.tmpl" .}} diff --git a/pkg/codegen/templates/handler/echo-v5/middleware.tmpl b/pkg/codegen/templates/handler/echo-v5/middleware.tmpl new file mode 100644 index 00000000..cb6e6434 --- /dev/null +++ b/pkg/codegen/templates/handler/echo-v5/middleware.tmpl @@ -0,0 +1,46 @@ +{{/* +Copyright 2026 DoorDash, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/}} +{{- $config := .Config -}} +// Package {{ $config.PackageName }} This file is generated ONCE as a starting point and will NOT be overwritten. +// Modify it freely to add your middleware logic. +// To regenerate, delete this file or set generate.handler.output.overwrite: true in config. +// +// Echo provides many built-in middleware: middleware.Logger(), middleware.Recover(), +// middleware.RequestID(), middleware.CORS(), middleware.Timeout(), etc. +// See: https://echo.labstack.com/docs/middleware +// +// This file shows how to write custom middleware using echo.MiddlewareFunc. +package {{ $config.PackageName }} + +import ( + "log" + + "github.com/labstack/echo/v5" +) + +// ExampleMiddleware demonstrates a custom echo.MiddlewareFunc. +// It logs before and after each request. +func ExampleMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + log.Printf("before: %s %s", c.Request().Method, c.Request().URL.Path) + err := next(c) + resp, _ := echo.UnwrapResponse(c.Response()) + log.Printf("after: %s %s status=%d", c.Request().Method, c.Request().URL.Path, resp.Status) + return err + } + } +} diff --git a/pkg/codegen/templates/handler/echo-v5/server.tmpl b/pkg/codegen/templates/handler/echo-v5/server.tmpl new file mode 100644 index 00000000..86569040 --- /dev/null +++ b/pkg/codegen/templates/handler/echo-v5/server.tmpl @@ -0,0 +1,95 @@ +{{/* +Copyright 2026 DoorDash, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/}} +{{define "server-main"}} +{{- $config := .Config -}} +{{- $server := .ServerOptions -}} +{{- /* Types prefix: when models-package-alias is set, types are in a separate package */ -}} +{{- $modelsAlias := $config.Generate.Handler.ModelsPackageAlias -}} +{{- $typesPrefix := "handler." -}} +{{- if $modelsAlias -}} +{{- $typesPrefix = printf "%s." $modelsAlias -}} +{{- end -}} +// Package main - This file is generated ONCE as a starting point and will NOT be overwritten. +// Modify it freely to customize your server setup. +// To regenerate, delete this file or set generate.handler.output.overwrite: true in config. +package main + +import ( + "context" + "log" + "net/http" + "os/signal" + "syscall" + "time" + + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" + handler "{{ $server.HandlerPackage }}" +) + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + // Create Echo instance + e := echo.New() + + // Add Echo built-in middleware + e.Use(middleware.Recover()) + e.Use(middleware.RequestID()) + e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + LogMethod: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + log.Printf("%s %s %d", v.Method, v.URI, v.Status) + return nil + }, + })) + e.Use(middleware.CORS("*")) + e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ + Timeout: {{ $server.Timeout }} * time.Second, + })) + {{- if $config.Generate.Handler.Middleware }} + + // Add custom middleware from generated scaffold + e.Use(handler.ExampleMiddleware()) + {{- end }} + + // Create your service implementation + svc := handler.New{{ $config.Generate.Handler.Name }}() + + // Register routes + {{ $typesPrefix }}NewRouter(e, svc) + + // Start server with graceful shutdown + sc := echo.StartConfig{ + HideBanner: true, + Address: ":{{ $server.Port }}", + BeforeServeFunc: func(s *http.Server) error { + s.ReadTimeout = 5 * time.Second + s.WriteTimeout = {{ $server.Timeout }} * time.Second + s.IdleTimeout = 120 * time.Second + return nil + }, + } + log.Printf("Starting server on :%d", {{ $server.Port }}) + if err := sc.Start(ctx, e); err != nil { + log.Printf("Server stopped: %v", err) + } +} +{{end}} +{{template "server-main" .}}