diff --git a/README.md b/README.md index a3bb1dea..412d3580 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ http.ListenAndServe(":8080", api.NewRouter(pets{})) | [Server](docs/server.md) | the service interface, the HTTP adapter, 14 routers, starter files | | [Client](docs/client.md) | one method per operation, envelopes, streams | | [MCP](docs/mcp.md) | MCP tools over the client | +| [Observability](docs/observability.md) | tracing and metrics with OpenTelemetry, on server and client | | [Templates](docs/templates.md) | replacing template blocks, extra files from your own templates | | [Migration](docs/migration.md) | moving from another generator | diff --git a/docs/frameworks.md b/docs/frameworks.md index 038037ec..76286337 100644 --- a/docs/frameworks.md +++ b/docs/frameworks.md @@ -114,19 +114,38 @@ The `framework` package holds what the rules share: ```go type Handler struct { - Signature string // after the name: "(w http.ResponseWriter, r *http.Request)" or "(c echo.Context) error" - Prologue string // binds w and r when the parameters are others: "w, r := c.Response(), c.Request()" - Return string // leaves the handler early: "return" or "return nil" - Epilogue string // ends the handler: "" or "return nil" + Signature string // after the name: "(w http.ResponseWriter, r *http.Request)" or "(c echo.Context) error" + Writer string // the handler's response writer: "w" or "c.Response()" + Request string // the handler's request: "r" or "c.Request()" + Epilogue string // ends the handler: "" or "return nil" + ServeSignature string // the serve method's shape: "(w http.ResponseWriter, r *http.Request)" + Context string // the framework's context: "" or "c" + ContextServeSignature string // the same, with Context first: "(c echo.Context, w http.ResponseWriter, ...)" } ``` -The adapter template writes every handler as `func (a *HTTPAdapter) Op {`, then the -prologue, then the decoding of the request, with `Return` on every early exit. +The adapter template writes two methods per operation: + +```go +func (a *HTTPAdapter) GetPet(c echo.Context) error { + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c echo.Context, w http.ResponseWriter, r *http.Request) { + // decodes the request, calls the service, writes the response +} +``` + +`Serve` runs the serve method inside the [operation middleware](server.md#operation-middleware). +An operation without path parameters does not need the context, so its serve method takes +`ServeSignature` and the handler passes `a.serveListPets` as it is. ### Names in a handler -The body of a handler always names the response writer `w`, the request `r` and the framework's +The serve method always names the response writer `w`, the request `r` and the framework's context `c`. So `PathParam` may use them. `PathParam` has to give the value unescaped once, whatever the router hands over. For a router that @@ -135,8 +154,8 @@ cuts values from the raw path, use `runtime.UnescapePath`. ### Two shapes - A framework whose handlers are `http.HandlerFunc`s returns `framework.HTTPHandler(s)`. -- A framework whose handlers take its context returns `framework.ContextHandler`, with the - context's type spelled through `s.Import`. +- A framework whose handlers take its context returns `framework.ContextHandler(s, ctxType)`, + with the context's type spelled through `s.Import`. Handlers of the second kind read their errors from the error handler. They write every failed request themselves and return nil, so an operation answers the same under every framework. diff --git a/docs/migration/ogen.md b/docs/migration/ogen.md index 267340f7..0aa588db 100644 --- a/docs/migration/ogen.md +++ b/docs/migration/ogen.md @@ -22,8 +22,10 @@ are built and tested. `WithJSONEncoder` on the server, take another library such as sonic. - A `uuid`, `uri` or `ipv4` format is a `string` checked by `Validate`, unless `models.format-types` names a type for it. -- There is no security handler and no OpenTelemetry. A middleware checks credentials, and a - wrapped `http.Client` traces the client. +- There is no security handler, and the generated code does not import OpenTelemetry. A + middleware checks credentials. `otelhttp` traces the server through `WithOperationMiddleware`, + with spans named after the operation as in ogen, and the client through `WithHTTPClient` + ([observability](../observability.md)). - The routes go on the router of `server.framework`. `std-http` is the standard library's `ServeMux`. diff --git a/docs/observability.md b/docs/observability.md new file mode 100644 index 00000000..f9dc2a63 --- /dev/null +++ b/docs/observability.md @@ -0,0 +1,73 @@ +# Observability + +The generated code imports no tracing or metrics library. It gives you two hooks, and you bring the +library. The recipes below use OpenTelemetry's `otelhttp`. + +| Side | Hook | +|---|---| +| server | `WithOperationMiddleware`, see [operation middleware](server.md#operation-middleware) | +| client | `WithHTTPClient`, see [HTTP client](client.md#http-client) | + +## Operation names + +Server and client put the name of the operation, `GetPet`, on the request's context. +`runtime.OperationID(ctx)` reads it. Use it as a span name, a metric label or a log field. + +```go +func operationName(_ string, r *http.Request) string { + return runtime.OperationID(r.Context()) +} +``` + +## Server + +```go +import ( + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/attribute" + + "github.com/mockzilla/mockzilla-codegen/pkg/runtime" +) + +func labelOperation(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + labeler, _ := otelhttp.LabelerFromContext(r.Context()) + labeler.Add(attribute.String("operation", runtime.OperationID(r.Context()))) + next.ServeHTTP(w, r) + }) +} + +tracing := otelhttp.NewMiddleware("", otelhttp.WithSpanNameFormatter(operationName)) +router := NewRouter(svc, WithOperationMiddleware(tracing, labelOperation)) +``` + +- Each request gets a server span named `GetPet`. +- The span covers reading the request, the service and writing the response. A 400 for a bad + parameter is in it too. +- The service's `ctx` holds the span, so the spans the service starts are its children. +- `labelOperation` adds `operation="GetPet"` to otelhttp's request metrics, such as + `http.server.request.duration`. Leave it out if you only want spans. + +`otelhttp.NewMiddleware` also fits `WithMiddleware`. There it runs before the route is picked, so +its spans cannot be named after the operation. + +## Client + +```go +httpClient := &http.Client{ + Transport: otelhttp.NewTransport(http.DefaultTransport, otelhttp.WithSpanNameFormatter(operationName)), +} +c, err := NewPetClient(baseURL, WithHTTPClient(httpClient)) +``` + +Each call gets a client span named `GetPet`. + +## One trace across services + +Set a propagator once, in the client's program and in the server's: + +```go +otel.SetTextMapPropagator(propagation.TraceContext{}) +``` + +The client then sends a `traceparent` header, and the server span joins the caller's trace. diff --git a/docs/server.md b/docs/server.md index a949afda..f8e2b318 100644 --- a/docs/server.md +++ b/docs/server.md @@ -265,7 +265,8 @@ A property of a JSON, form or multipart body gets its default the same way, see A handler first puts the name of its operation, `ListPets`, on the request's context. The service, a wrapper of it and the error handler read it with `runtime.OperationID(ctx)`. -The router's middleware runs before the handler, so it does not see the name. +The router's middleware runs before the handler, so it does not see the name. Operation +middleware does, see [operation middleware](#operation-middleware). ### Reading the body @@ -362,6 +363,7 @@ Options are set with `ServerOption` functions on the adapter and on the router a | Option | Sets | |---|---| | `WithMiddleware(mw...)` | `func(http.Handler) http.Handler` wrappers, outermost first | +| `WithOperationMiddleware(mw...)` | the same wrappers around each operation, see [operation middleware](#operation-middleware) | | `WithErrorHandler(h)` | what writes failed requests, `DefaultErrorHandler{}` by default | | `WithJSONDecoder(fn)` | what reads JSON request bodies, `json.Unmarshal` by default | | `WithJSONEncoder(fn)` | what writes JSON response bodies and stream frames, `json.Marshal` by default | @@ -630,6 +632,39 @@ wraps those routes only: A router can make a redirect on its own, for a trailing slash or a path it cleans. On gin, gorilla-mux, kratos, iris, fasthttp and hertz, that redirect is answered before the middleware. +### Operation middleware + +`WithOperationMiddleware` takes the same `func(http.Handler) http.Handler` as `WithMiddleware`. +It wraps each operation, once the operation's name is on the context: + +```go +func logOperation(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + slog.InfoContext(r.Context(), "served", "operation", runtime.OperationID(r.Context()), "took", time.Since(start)) + }) +} + +router := NewRouter(svc, WithOperationMiddleware(logOperation)) +``` + +| | `WithMiddleware` | `WithOperationMiddleware` | +|---|---|---| +| Runs | before the router picks a route | after it, around one operation | +| Sees the operation name | no | yes | +| Wraps unknown paths | on a new router | no | +| Wraps a 400 for a bad parameter or body | yes | yes | + +- Each middleware is called once, when the adapter is made. So it can hold a tracer or a counter. +- It passes on the request it got, or one from `r.WithContext` with a context made from + `r.Context()`. A request with a context of its own panics with `httpserver.ErrContextLost`. +- A middleware that wraps the writer keeps its `Flush`, or gives an `Unwrap` method. Otherwise the + frames of a stream are not flushed as they go. +- On echo and kratos too, the operation gets the writer and the request the middleware passes on. + +For tracing and metrics, see [observability](observability.md). + ### Handlers Most routers take the handlers as `http.HandlerFunc`s: some directly, the others through a small diff --git a/examples/bodies/checked/gen.go b/examples/bodies/checked/gen.go index 10452688..4dae5a9f 100644 --- a/examples/bodies/checked/gen.go +++ b/examples/bodies/checked/gen.go @@ -257,13 +257,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -291,6 +292,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -328,8 +336,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -341,12 +350,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddPet handles POST /pets. func (a *HTTPAdapter) AddPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddPet")) + a.operations.Serve(w, r, "AddPet", a.serveAddPet) +} + +func (a *HTTPAdapter) serveAddPet(w http.ResponseWriter, r *http.Request) { opts := &AddPetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -395,7 +408,10 @@ func (a *HTTPAdapter) AddPet(w http.ResponseWriter, r *http.Request) { // Upload handles POST /uploads. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": diff --git a/examples/bodies/defaults/gen.go b/examples/bodies/defaults/gen.go index e9210be7..adcebd8f 100644 --- a/examples/bodies/defaults/gen.go +++ b/examples/bodies/defaults/gen.go @@ -148,13 +148,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -182,6 +183,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -219,8 +227,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -232,12 +241,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddOrder handles POST /orders. func (a *HTTPAdapter) AddOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddOrder")) + a.operations.Serve(w, r, "AddOrder", a.serveAddOrder) +} + +func (a *HTTPAdapter) serveAddOrder(w http.ResponseWriter, r *http.Request) { opts := &AddOrderServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/bodies/encoding/gen.go b/examples/bodies/encoding/gen.go index 25d990e1..38d297a0 100644 --- a/examples/bodies/encoding/gen.go +++ b/examples/bodies/encoding/gen.go @@ -562,13 +562,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -596,6 +597,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -633,8 +641,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -646,12 +655,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // SendParcel handles POST /parcels. func (a *HTTPAdapter) SendParcel(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "SendParcel")) + a.operations.Serve(w, r, "SendParcel", a.serveSendParcel) +} + +func (a *HTTPAdapter) serveSendParcel(w http.ResponseWriter, r *http.Request) { opts := &SendParcelServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -700,7 +713,10 @@ func (a *HTTPAdapter) SendParcel(w http.ResponseWriter, r *http.Request) { // GetParcel handles GET /parcels/{id}. func (a *HTTPAdapter) GetParcel(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetParcel")) + a.operations.Serve(w, r, "GetParcel", a.serveGetParcel) +} + +func (a *HTTPAdapter) serveGetParcel(w http.ResponseWriter, r *http.Request) { opts := &GetParcelServiceRequestOptions{RawRequest: r} opts.PathParams = &GetParcelPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -726,7 +742,10 @@ func (a *HTTPAdapter) GetParcel(w http.ResponseWriter, r *http.Request) { // PrintLabel handles POST /labels. func (a *HTTPAdapter) PrintLabel(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PrintLabel")) + a.operations.Serve(w, r, "PrintLabel", a.servePrintLabel) +} + +func (a *HTTPAdapter) servePrintLabel(w http.ResponseWriter, r *http.Request) { opts := &PrintLabelServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -766,7 +785,10 @@ func (a *HTTPAdapter) PrintLabel(w http.ResponseWriter, r *http.Request) { // CreateCustomer handles POST /customers. func (a *HTTPAdapter) CreateCustomer(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateCustomer")) + a.operations.Serve(w, r, "CreateCustomer", a.serveCreateCustomer) +} + +func (a *HTTPAdapter) serveCreateCustomer(w http.ResponseWriter, r *http.Request) { opts := &CreateCustomerServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": diff --git a/examples/bodies/ranges/gen.go b/examples/bodies/ranges/gen.go index 7a2b0d98..c7e7c6f1 100644 --- a/examples/bodies/ranges/gen.go +++ b/examples/bodies/ranges/gen.go @@ -309,12 +309,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -341,6 +342,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -371,8 +379,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -384,12 +393,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddAttachment handles POST /attachments. func (a *HTTPAdapter) AddAttachment(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddAttachment")) + a.operations.Serve(w, r, "AddAttachment", a.serveAddAttachment) +} + +func (a *HTTPAdapter) serveAddAttachment(w http.ResponseWriter, r *http.Request) { opts := &AddAttachmentServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); { case contentType == "": @@ -428,7 +441,10 @@ func (a *HTTPAdapter) AddAttachment(w http.ResponseWriter, r *http.Request) { // PutScore handles PUT /scores. func (a *HTTPAdapter) PutScore(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutScore")) + a.operations.Serve(w, r, "PutScore", a.servePutScore) +} + +func (a *HTTPAdapter) servePutScore(w http.ResponseWriter, r *http.Request) { opts := &PutScoreServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -458,7 +474,10 @@ func (a *HTTPAdapter) PutScore(w http.ResponseWriter, r *http.Request) { // AddCard handles POST /cards. func (a *HTTPAdapter) AddCard(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddCard")) + a.operations.Serve(w, r, "AddCard", a.serveAddCard) +} + +func (a *HTTPAdapter) serveAddCard(w http.ResponseWriter, r *http.Request) { opts := &AddCardServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); { case contentType == "": @@ -489,7 +508,10 @@ func (a *HTTPAdapter) AddCard(w http.ResponseWriter, r *http.Request) { // AddEvent handles POST /events. func (a *HTTPAdapter) AddEvent(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddEvent")) + a.operations.Serve(w, r, "AddEvent", a.serveAddEvent) +} + +func (a *HTTPAdapter) serveAddEvent(w http.ResponseWriter, r *http.Request) { opts := &AddEventServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); { case contentType == "": diff --git a/examples/bodies/responses/gen.go b/examples/bodies/responses/gen.go index 695cea9f..298fbe0e 100644 --- a/examples/bodies/responses/gen.go +++ b/examples/bodies/responses/gen.go @@ -227,12 +227,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -259,6 +260,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -289,8 +297,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -302,12 +311,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) @@ -324,7 +337,10 @@ func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { // GetMotd handles GET /motd. func (a *HTTPAdapter) GetMotd(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMotd")) + a.operations.Serve(w, r, "GetMotd", a.serveGetMotd) +} + +func (a *HTTPAdapter) serveGetMotd(w http.ResponseWriter, r *http.Request) { opts := &GetMotdServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMotd(r.Context(), opts) @@ -341,7 +357,10 @@ func (a *HTTPAdapter) GetMotd(w http.ResponseWriter, r *http.Request) { // GetProfile handles GET /profile. func (a *HTTPAdapter) GetProfile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProfile")) + a.operations.Serve(w, r, "GetProfile", a.serveGetProfile) +} + +func (a *HTTPAdapter) serveGetProfile(w http.ResponseWriter, r *http.Request) { opts := &GetProfileServiceRequestOptions{RawRequest: r} res, err := a.svc.GetProfile(r.Context(), opts) diff --git a/examples/layout/by-family/server/gen.go b/examples/layout/by-family/server/gen.go index 936d9c80..16b34fcc 100644 --- a/examples/layout/by-family/server/gen.go +++ b/examples/layout/by-family/server/gen.go @@ -174,12 +174,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -206,6 +207,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -236,8 +244,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -249,12 +258,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // CreateOrder handles POST /orders. func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateOrder")) + a.operations.Serve(w, r, "CreateOrder", a.serveCreateOrder) +} + +func (a *HTTPAdapter) serveCreateOrder(w http.ResponseWriter, r *http.Request) { opts := &CreateOrderServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -284,7 +297,10 @@ func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { // GetOrder handles GET /orders/{id}. func (a *HTTPAdapter) GetOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetOrder")) + a.operations.Serve(w, r, "GetOrder", a.serveGetOrder) +} + +func (a *HTTPAdapter) serveGetOrder(w http.ResponseWriter, r *http.Request) { opts := &GetOrderServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &models.GetOrderPathParams{} diff --git a/examples/layout/service-apart/api/gen.go b/examples/layout/service-apart/api/gen.go index 4e1c18f7..357362a0 100644 --- a/examples/layout/service-apart/api/gen.go +++ b/examples/layout/service-apart/api/gen.go @@ -36,12 +36,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -68,6 +69,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -98,8 +106,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc service.ServiceInterface - opts *ServerOptions + svc service.ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -111,12 +120,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc service.ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // CreateOrder handles POST /orders. func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateOrder")) + a.operations.Serve(w, r, "CreateOrder", a.serveCreateOrder) +} + +func (a *HTTPAdapter) serveCreateOrder(w http.ResponseWriter, r *http.Request) { opts := &service.CreateOrderServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -146,7 +159,10 @@ func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { // GetOrder handles GET /orders/{id}. func (a *HTTPAdapter) GetOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetOrder")) + a.operations.Serve(w, r, "GetOrder", a.serveGetOrder) +} + +func (a *HTTPAdapter) serveGetOrder(w http.ResponseWriter, r *http.Request) { opts := &service.GetOrderServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &models.GetOrderPathParams{} diff --git a/examples/layout/split-files/server.gen.go b/examples/layout/split-files/server.gen.go index fa9b2654..e193b25d 100644 --- a/examples/layout/split-files/server.gen.go +++ b/examples/layout/split-files/server.gen.go @@ -173,12 +173,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -205,6 +206,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -235,8 +243,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -248,12 +257,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // CreateOrder handles POST /orders. func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateOrder")) + a.operations.Serve(w, r, "CreateOrder", a.serveCreateOrder) +} + +func (a *HTTPAdapter) serveCreateOrder(w http.ResponseWriter, r *http.Request) { opts := &CreateOrderServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -283,7 +296,10 @@ func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { // GetOrder handles GET /orders/{id}. func (a *HTTPAdapter) GetOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetOrder")) + a.operations.Serve(w, r, "GetOrder", a.serveGetOrder) +} + +func (a *HTTPAdapter) serveGetOrder(w http.ResponseWriter, r *http.Request) { opts := &GetOrderServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetOrderPathParams{} diff --git a/examples/layout/two-packages/api/gen.go b/examples/layout/two-packages/api/gen.go index a019809a..75168bfa 100644 --- a/examples/layout/two-packages/api/gen.go +++ b/examples/layout/two-packages/api/gen.go @@ -178,12 +178,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -210,6 +211,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -240,8 +248,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -253,12 +262,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // CreateOrder handles POST /orders. func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateOrder")) + a.operations.Serve(w, r, "CreateOrder", a.serveCreateOrder) +} + +func (a *HTTPAdapter) serveCreateOrder(w http.ResponseWriter, r *http.Request) { opts := &CreateOrderServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -288,7 +301,10 @@ func (a *HTTPAdapter) CreateOrder(w http.ResponseWriter, r *http.Request) { // GetOrder handles GET /orders/{id}. func (a *HTTPAdapter) GetOrder(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetOrder")) + a.operations.Serve(w, r, "GetOrder", a.serveGetOrder) +} + +func (a *HTTPAdapter) serveGetOrder(w http.ResponseWriter, r *http.Request) { opts := &GetOrderServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &models.GetOrderPathParams{} diff --git a/examples/mcp/basic/gen.go b/examples/mcp/basic/gen.go index 88070961..659ff35b 100644 --- a/examples/mcp/basic/gen.go +++ b/examples/mcp/basic/gen.go @@ -458,12 +458,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -490,6 +491,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -520,8 +528,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -533,12 +542,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -561,7 +574,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -596,7 +612,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -623,7 +642,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -645,7 +667,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/mcp/defaults/gen.go b/examples/mcp/defaults/gen.go index 76577a81..2d22fb19 100644 --- a/examples/mcp/defaults/gen.go +++ b/examples/mcp/defaults/gen.go @@ -163,12 +163,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -195,6 +196,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -225,8 +233,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -238,12 +247,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &SearchQuery{} diff --git a/examples/mcp/discriminator/gen.go b/examples/mcp/discriminator/gen.go index 0576d5e5..27d4ddfc 100644 --- a/examples/mcp/discriminator/gen.go +++ b/examples/mcp/discriminator/gen.go @@ -174,12 +174,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -206,6 +207,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -236,8 +244,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -249,12 +258,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddAnimal handles POST /animals. func (a *HTTPAdapter) AddAnimal(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddAnimal")) + a.operations.Serve(w, r, "AddAnimal", a.serveAddAnimal) +} + +func (a *HTTPAdapter) serveAddAnimal(w http.ResponseWriter, r *http.Request) { opts := &AddAnimalServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/mcp/files/gen.go b/examples/mcp/files/gen.go index 075b44e0..4cd3d14c 100644 --- a/examples/mcp/files/gen.go +++ b/examples/mcp/files/gen.go @@ -187,12 +187,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -219,6 +220,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -249,8 +257,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -262,12 +271,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // SetPhoto handles PUT /pets/{id}/photo. func (a *HTTPAdapter) SetPhoto(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "SetPhoto")) + a.operations.Serve(w, r, "SetPhoto", a.serveSetPhoto) +} + +func (a *HTTPAdapter) serveSetPhoto(w http.ResponseWriter, r *http.Request) { opts := &SetPhotoServiceRequestOptions{RawRequest: r} opts.PathParams = &SetPhotoPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -304,7 +317,10 @@ func (a *HTTPAdapter) SetPhoto(w http.ResponseWriter, r *http.Request) { // AddNote handles POST /notes. func (a *HTTPAdapter) AddNote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddNote")) + a.operations.Serve(w, r, "AddNote", a.serveAddNote) +} + +func (a *HTTPAdapter) serveAddNote(w http.ResponseWriter, r *http.Request) { opts := &AddNoteServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": diff --git a/examples/mcp/nullable/gen.go b/examples/mcp/nullable/gen.go index 38fff34e..d564b887 100644 --- a/examples/mcp/nullable/gen.go +++ b/examples/mcp/nullable/gen.go @@ -152,12 +152,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -184,6 +185,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -214,8 +222,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -227,12 +236,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // Adopt handles POST /adoptions. func (a *HTTPAdapter) Adopt(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Adopt")) + a.operations.Serve(w, r, "Adopt", a.serveAdopt) +} + +func (a *HTTPAdapter) serveAdopt(w http.ResponseWriter, r *http.Request) { opts := &AdoptServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/mcp/patterns/gen.go b/examples/mcp/patterns/gen.go index 10d5b5e2..06445046 100644 --- a/examples/mcp/patterns/gen.go +++ b/examples/mcp/patterns/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddTag handles POST /tags. func (a *HTTPAdapter) AddTag(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddTag")) + a.operations.Serve(w, r, "AddTag", a.serveAddTag) +} + +func (a *HTTPAdapter) serveAddTag(w http.ResponseWriter, r *http.Request) { opts := &AddTagServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &AddTagQuery{} diff --git a/examples/mcp/readonly/gen.go b/examples/mcp/readonly/gen.go index 5fe4a5fc..92ee7485 100644 --- a/examples/mcp/readonly/gen.go +++ b/examples/mcp/readonly/gen.go @@ -119,12 +119,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -151,6 +152,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -181,8 +189,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -194,12 +203,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // AddPet handles POST /pets. func (a *HTTPAdapter) AddPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "AddPet")) + a.operations.Serve(w, r, "AddPet", a.serveAddPet) +} + +func (a *HTTPAdapter) serveAddPet(w http.ResponseWriter, r *http.Request) { opts := &AddPetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/mcp/results/gen.go b/examples/mcp/results/gen.go index 7c21ecb4..96f03090 100644 --- a/examples/mcp/results/gen.go +++ b/examples/mcp/results/gen.go @@ -306,12 +306,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -338,6 +339,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -368,8 +376,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -381,12 +390,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // CountPets handles GET /pets/count. func (a *HTTPAdapter) CountPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CountPets")) + a.operations.Serve(w, r, "CountPets", a.serveCountPets) +} + +func (a *HTTPAdapter) serveCountPets(w http.ResponseWriter, r *http.Request) { opts := &CountPetsServiceRequestOptions{RawRequest: r} res, err := a.svc.CountPets(r.Context(), opts) @@ -403,7 +416,10 @@ func (a *HTTPAdapter) CountPets(w http.ResponseWriter, r *http.Request) { // FindPet handles GET /pets/find. func (a *HTTPAdapter) FindPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "FindPet")) + a.operations.Serve(w, r, "FindPet", a.serveFindPet) +} + +func (a *HTTPAdapter) serveFindPet(w http.ResponseWriter, r *http.Request) { opts := &FindPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &FindPetQuery{} @@ -426,7 +442,10 @@ func (a *HTTPAdapter) FindPet(w http.ResponseWriter, r *http.Request) { // GetPhoto handles GET /pets/photo. func (a *HTTPAdapter) GetPhoto(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPhoto")) + a.operations.Serve(w, r, "GetPhoto", a.serveGetPhoto) +} + +func (a *HTTPAdapter) serveGetPhoto(w http.ResponseWriter, r *http.Request) { opts := &GetPhotoServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &GetPhotoQuery{} @@ -449,7 +468,10 @@ func (a *HTTPAdapter) GetPhoto(w http.ResponseWriter, r *http.Request) { // GetIcon handles GET /pets/icon. func (a *HTTPAdapter) GetIcon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetIcon")) + a.operations.Serve(w, r, "GetIcon", a.serveGetIcon) +} + +func (a *HTTPAdapter) serveGetIcon(w http.ResponseWriter, r *http.Request) { opts := &GetIconServiceRequestOptions{RawRequest: r} res, err := a.svc.GetIcon(r.Context(), opts) diff --git a/examples/mcp/select/gen.go b/examples/mcp/select/gen.go index dc057340..a745bfbc 100644 --- a/examples/mcp/select/gen.go +++ b/examples/mcp/select/gen.go @@ -317,12 +317,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -349,6 +350,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -379,8 +387,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -392,12 +401,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetItem handles GET /items/{id}. func (a *HTTPAdapter) GetItem(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetItem")) + a.operations.Serve(w, r, "GetItem", a.serveGetItem) +} + +func (a *HTTPAdapter) serveGetItem(w http.ResponseWriter, r *http.Request) { opts := &GetItemServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetItemPathParams{} @@ -430,7 +443,10 @@ func (a *HTTPAdapter) GetItem(w http.ResponseWriter, r *http.Request) { // PutItem handles PUT /items/{id}. func (a *HTTPAdapter) PutItem(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutItem")) + a.operations.Serve(w, r, "PutItem", a.servePutItem) +} + +func (a *HTTPAdapter) servePutItem(w http.ResponseWriter, r *http.Request) { opts := &PutItemServiceRequestOptions{RawRequest: r} opts.PathParams = &PutItemPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -465,7 +481,10 @@ func (a *HTTPAdapter) PutItem(w http.ResponseWriter, r *http.Request) { // DeleteItem handles DELETE /items/{id}. func (a *HTTPAdapter) DeleteItem(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteItem")) + a.operations.Serve(w, r, "DeleteItem", a.serveDeleteItem) +} + +func (a *HTTPAdapter) serveDeleteItem(w http.ResponseWriter, r *http.Request) { opts := &DeleteItemServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteItemPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -487,7 +506,10 @@ func (a *HTTPAdapter) DeleteItem(w http.ResponseWriter, r *http.Request) { // Reset handles POST /internal/reset. func (a *HTTPAdapter) Reset(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Reset")) + a.operations.Serve(w, r, "Reset", a.serveReset) +} + +func (a *HTTPAdapter) serveReset(w http.ResponseWriter, r *http.Request) { opts := &ResetServiceRequestOptions{RawRequest: r} res, err := a.svc.Reset(r.Context(), opts) diff --git a/examples/mcp/streaming/gen.go b/examples/mcp/streaming/gen.go index 43a7e7e0..d7d5c820 100644 --- a/examples/mcp/streaming/gen.go +++ b/examples/mcp/streaming/gen.go @@ -190,12 +190,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -222,6 +223,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -252,8 +260,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -265,12 +274,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // Chat handles POST /chat. func (a *HTTPAdapter) Chat(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Chat")) + a.operations.Serve(w, r, "Chat", a.serveChat) +} + +func (a *HTTPAdapter) serveChat(w http.ResponseWriter, r *http.Request) { opts := &ChatServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -300,7 +313,10 @@ func (a *HTTPAdapter) Chat(w http.ResponseWriter, r *http.Request) { // ListEvents handles GET /events. func (a *HTTPAdapter) ListEvents(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListEvents")) + a.operations.Serve(w, r, "ListEvents", a.serveListEvents) +} + +func (a *HTTPAdapter) serveListEvents(w http.ResponseWriter, r *http.Request) { opts := &ListEventsServiceRequestOptions{RawRequest: r} res, err := a.svc.ListEvents(r.Context(), opts) diff --git a/examples/migration/oapi-codegen-dd/gen.go b/examples/migration/oapi-codegen-dd/gen.go index ea21d648..8472ddae 100644 --- a/examples/migration/oapi-codegen-dd/gen.go +++ b/examples/migration/oapi-codegen-dd/gen.go @@ -436,13 +436,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -470,6 +471,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -507,8 +515,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -520,12 +529,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -561,7 +574,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -605,7 +621,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -636,7 +655,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/migration/oapi-codegen/gen.go b/examples/migration/oapi-codegen/gen.go index 5513f364..28213600 100644 --- a/examples/migration/oapi-codegen/gen.go +++ b/examples/migration/oapi-codegen/gen.go @@ -423,12 +423,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -455,6 +456,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -485,8 +493,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -498,12 +507,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -535,7 +548,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -570,7 +586,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -597,7 +616,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/migration/ogen/gen.go b/examples/migration/ogen/gen.go index 55dfa3b7..97c7c32f 100644 --- a/examples/migration/ogen/gen.go +++ b/examples/migration/ogen/gen.go @@ -422,12 +422,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -454,6 +455,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -484,8 +492,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -497,12 +506,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -534,7 +547,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -569,7 +585,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -596,7 +615,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/models/nullable/gen.go b/examples/models/nullable/gen.go index 9b7e4cd0..3245b03a 100644 --- a/examples/models/nullable/gen.go +++ b/examples/models/nullable/gen.go @@ -191,13 +191,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -225,6 +226,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -262,8 +270,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -275,12 +284,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // UpdatePet handles PATCH /pets/{id}. func (a *HTTPAdapter) UpdatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "UpdatePet")) + a.operations.Serve(w, r, "UpdatePet", a.serveUpdatePet) +} + +func (a *HTTPAdapter) serveUpdatePet(w http.ResponseWriter, r *http.Request) { opts := &UpdatePetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &UpdatePetPathParams{} diff --git a/examples/server/beego/basic/gen.go b/examples/server/beego/basic/gen.go index a3d20db5..f63a5a41 100644 --- a/examples/server/beego/basic/gen.go +++ b/examples/server/beego/basic/gen.go @@ -374,12 +374,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -406,6 +407,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -436,8 +444,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -449,12 +458,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -477,7 +490,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -507,7 +523,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -529,7 +548,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -551,7 +573,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/beego/basic/gen_test.go b/examples/server/beego/basic/gen_test.go index 72ec3e15..fcfe9ace 100644 --- a/examples/server/beego/basic/gen_test.go +++ b/examples/server/beego/basic/gen_test.go @@ -70,6 +70,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/beego/bodies/gen.go b/examples/server/beego/bodies/gen.go index ebde56a3..79eb1cb8 100644 --- a/examples/server/beego/bodies/gen.go +++ b/examples/server/beego/bodies/gen.go @@ -674,12 +674,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -706,6 +707,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -736,8 +744,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -749,12 +758,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -782,7 +795,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -799,7 +815,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -829,7 +848,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -846,7 +868,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -863,7 +888,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -880,7 +908,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -911,7 +942,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -948,7 +982,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -980,7 +1017,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/beego/errors/gen.go b/examples/server/beego/errors/gen.go index f1d59b9c..530f2ded 100644 --- a/examples/server/beego/errors/gen.go +++ b/examples/server/beego/errors/gen.go @@ -302,13 +302,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -336,6 +337,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -373,8 +381,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -386,12 +395,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -428,7 +441,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/beego/params/gen.go b/examples/server/beego/params/gen.go index 6aef1642..d809dc23 100644 --- a/examples/server/beego/params/gen.go +++ b/examples/server/beego/params/gen.go @@ -607,12 +607,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -639,6 +640,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -669,8 +677,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -682,12 +691,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -721,7 +734,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -784,7 +800,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -818,7 +837,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -844,7 +866,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/beego/paths/gen.go b/examples/server/beego/paths/gen.go index d65705c1..71399ffc 100644 --- a/examples/server/beego/paths/gen.go +++ b/examples/server/beego/paths/gen.go @@ -810,12 +810,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -842,6 +843,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -872,8 +880,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -885,12 +894,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -918,7 +931,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -951,7 +967,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -973,7 +992,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -995,7 +1017,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1017,7 +1042,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1039,7 +1067,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1056,7 +1087,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1078,7 +1112,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1104,7 +1141,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1126,7 +1166,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1143,7 +1186,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/beego/scaffolds/gen.go b/examples/server/beego/scaffolds/gen.go index d348f3f3..3dcd1fd2 100644 --- a/examples/server/beego/scaffolds/gen.go +++ b/examples/server/beego/scaffolds/gen.go @@ -168,12 +168,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -200,6 +201,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -230,8 +238,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -243,12 +252,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -265,7 +278,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/beego/split/api/gen.go b/examples/server/beego/split/api/gen.go index 33e5840f..ff64835f 100644 --- a/examples/server/beego/split/api/gen.go +++ b/examples/server/beego/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/chi/basic/gen.go b/examples/server/chi/basic/gen.go index 155495c1..f51926ef 100644 --- a/examples/server/chi/basic/gen.go +++ b/examples/server/chi/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,12 +455,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -474,7 +487,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -504,7 +520,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -526,7 +545,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -548,7 +570,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/chi/basic/gen_test.go b/examples/server/chi/basic/gen_test.go index a6501ba0..ba69e90f 100644 --- a/examples/server/chi/basic/gen_test.go +++ b/examples/server/chi/basic/gen_test.go @@ -78,6 +78,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestWithRouterAndMiddleware(t *testing.T) { t.Parallel() diff --git a/examples/server/chi/bodies/gen.go b/examples/server/chi/bodies/gen.go index 2ea1b579..9e2231f6 100644 --- a/examples/server/chi/bodies/gen.go +++ b/examples/server/chi/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,12 +755,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -779,7 +792,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -796,7 +812,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -826,7 +845,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -843,7 +865,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -860,7 +885,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -877,7 +905,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -908,7 +939,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -945,7 +979,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -977,7 +1014,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/chi/errors/gen.go b/examples/server/chi/errors/gen.go index 0e7bd748..b2b1f987 100644 --- a/examples/server/chi/errors/gen.go +++ b/examples/server/chi/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,12 +392,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -425,7 +438,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/chi/params/gen.go b/examples/server/chi/params/gen.go index b42d0192..e6a846d0 100644 --- a/examples/server/chi/params/gen.go +++ b/examples/server/chi/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,12 +688,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "simple")), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -718,7 +731,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -781,7 +797,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -815,7 +834,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -841,7 +863,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/chi/paths/gen.go b/examples/server/chi/paths/gen.go index b7dd7eeb..f3685c92 100644 --- a/examples/server/chi/paths/gen.go +++ b/examples/server/chi/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,12 +891,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -915,7 +928,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -948,7 +964,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -970,7 +989,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -992,7 +1014,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1014,7 +1039,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1036,7 +1064,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1053,7 +1084,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "lat_lng")), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1075,7 +1109,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1101,7 +1138,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1123,7 +1163,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1140,7 +1183,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/chi/scaffolds/gen.go b/examples/server/chi/scaffolds/gen.go index c0a48945..653e57d0 100644 --- a/examples/server/chi/scaffolds/gen.go +++ b/examples/server/chi/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -262,7 +275,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/chi/split/api/gen.go b/examples/server/chi/split/api/gen.go index 855d04da..6086f939 100644 --- a/examples/server/chi/split/api/gen.go +++ b/examples/server/chi/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,12 +171,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "isbn")), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/contract/gen.go b/examples/server/contract/gen.go index be05d1e5..8338d320 100644 --- a/examples/server/contract/gen.go +++ b/examples/server/contract/gen.go @@ -450,12 +450,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -482,6 +483,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -512,8 +520,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -525,12 +534,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -563,7 +576,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -598,7 +614,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -620,7 +639,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -650,7 +672,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // GetPing handles GET /ping. func (a *HTTPAdapter) GetPing(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPing")) + a.operations.Serve(w, r, "GetPing", a.serveGetPing) +} + +func (a *HTTPAdapter) serveGetPing(w http.ResponseWriter, r *http.Request) { opts := &GetPingServiceRequestOptions{RawRequest: r} res, err := a.svc.GetPing(r.Context(), opts) diff --git a/examples/server/echo/basic/gen.go b/examples/server/echo/basic/gen.go index eab8a3a2..c8ff5ecc 100644 --- a/examples/server/echo/basic/gen.go +++ b/examples/server/echo/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,131 +455,146 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/basic/gen_test.go b/examples/server/echo/basic/gen_test.go index 49f42a55..edd3489a 100644 --- a/examples/server/echo/basic/gen_test.go +++ b/examples/server/echo/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("{\"message\":\"Not Found\"}\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/echo/bodies/gen.go b/examples/server/echo/bodies/gen.go index e1ef14d8..a33822fa 100644 --- a/examples/server/echo/bodies/gen.go +++ b/examples/server/echo/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,271 +755,293 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(c.Response(), c.Request(), "PostJSON", a.servePostJSON) + return nil +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PostJSON", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostJSON", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostJSON(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostJSON", res) - return nil } // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(c.Response(), c.Request(), "GetForm", a.serveGetForm) + return nil +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetForm", res) - return nil } // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(c.Response(), c.Request(), "PostForm", a.servePostForm) + return nil +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, true, nil); err != nil { a.failDecode(w, r, "PostForm", err) - return nil + return } case "": a.failDecode(w, r, "PostForm", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostForm", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostForm", res) - return nil } // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(c.Response(), c.Request(), "GetQuote", a.serveGetQuote) + return nil +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetQuote", res) - return nil } // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(c.Response(), c.Request(), "GetCount", a.serveGetCount) + return nil +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetCount", res) - return nil } // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(c.Response(), c.Request(), "ListNotes", a.serveListNotes) + return nil +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListNotes", res) - return nil } // Upload handles POST /upload. func (a *HTTPAdapter) Upload(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(c.Response(), c.Request(), "Upload", a.serveUpload) + return nil +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": opts.Body = &UploadRequestBody{} if err := runtime.DecodeMultipart(r, opts.Body, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "Upload", err) - return nil + return } case "": a.failDecode(w, r, "Upload", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Upload", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.Upload(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Upload", res) - return nil } // PostText handles POST /text. func (a *HTTPAdapter) PostText(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(c.Response(), c.Request(), "PostText", a.servePostText) + return nil +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyText = runtime.Ptr(PostTextRequestBody(text)) case "application/octet-stream": data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyOctetStream = data case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostText", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostText(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostText", res) - return nil } // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(c.Response(), c.Request(), "PutFile", a.servePutFile) + return nil +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": file, err := runtime.DecodeFile(r, true) if err != nil { a.failDecode(w, r, "PutFile", err) - return nil + return } opts.Body = runtime.Ptr(PutFileRequestBody(file)) case "": a.failDecode(w, r, "PutFile", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutFile", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PutFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutFile", res) - return nil } // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(c.Response(), c.Request(), "PostAny", a.servePostAny) + return nil +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyXML = runtime.Ptr(PostAnyXMLRequestBody(text)) case "text/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyTextXML = runtime.Ptr(PostAnyXMLRequestBody2(text)) case "": @@ -1018,7 +1049,7 @@ func (a *HTTPAdapter) PostAny(c echo.Context) error { data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyAny = data } @@ -1026,14 +1057,13 @@ func (a *HTTPAdapter) PostAny(c echo.Context) error { res, err := a.svc.PostAny(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostAny", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/errors/gen.go b/examples/server/echo/errors/gen.go index d5bb111c..2d41fed1 100644 --- a/examples/server/echo/errors/gen.go +++ b/examples/server/echo/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,28 +392,34 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetPetQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "fields", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Fields); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "fields", ParamLocation: "query", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "GetPet", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) @@ -412,50 +427,54 @@ func (a *HTTPAdapter) GetPet(c echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(c.Response(), c.Request(), "PutPet", func(w http.ResponseWriter, r *http.Request) { + a.servePutPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePutPet(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PutPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": body, err := a.opts.Presence.JSON(r.Body, runtime.Prop{Object: "Pet"}) if err != nil { a.failBody(w, r, "PutPet", err) - return nil + return } if err = runtime.DecodeJSON(body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PutPet", err) - return nil + return } case "": a.failDecode(w, r, "PutPet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutPet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "PutPet", Err: err}) - return nil + return } res, err := a.svc.PutPet(r.Context(), opts) @@ -463,17 +482,16 @@ func (a *HTTPAdapter) PutPet(c echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutPet", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/params/gen.go b/examples/server/echo/params/gen.go index 769e4030..ff6e2205 100644 --- a/examples/server/echo/params/gen.go +++ b/examples/server/echo/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,195 +688,208 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(c.Response(), c.Request(), "PathStyles", func(w http.ResponseWriter, r *http.Request) { + a.servePathStyles(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePathStyles(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("simple")), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "simple", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("label")), runtime.Param{Name: "label", Style: runtime.StyleLabel, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Label); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "label", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("matrix")), runtime.Param{Name: "matrix", Style: runtime.StyleMatrix, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Matrix); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "matrix", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("list")), runtime.Param{Name: "list", Style: runtime.StyleSimple, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.PathParams.List); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "list", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.PathStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PathStyles", res) - return nil } // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(c.Response(), c.Request(), "QueryStyles", a.serveQueryStyles) + return nil +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "form", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Form); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "form", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "csv", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Csv); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "csv", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "space", Style: runtime.StyleSpaceDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Space); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "space", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "pipe", Style: runtime.StylePipeDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Pipe); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "pipe", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "deep", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Deep); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "deep", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "flat", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Flat); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "flat", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "json", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: true}, &opts.Query.JSON); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "json", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "id", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.QueryStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "QueryStyles", res) - return nil } // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(c.Response(), c.Request(), "HeaderStyles", a.serveHeaderStyles) + return nil +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Tags", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Point", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XPoint); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Point", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-When", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XWhen); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-When", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Limit", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XLimit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Limit", ParamLocation: "header", Err: err}) - return nil + return } res, err := a.svc.HeaderStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "HeaderStyles", res) - return nil } // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(c.Response(), c.Request(), "CookieStyles", a.serveCookieStyles) + return nil +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "flags", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Cookies.Flags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "flags", ParamLocation: "cookie", Err: err}) - return nil + return } res, err := a.svc.CookieStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CookieStyles", res) - return nil } // Search handles GET /search. func (a *HTTPAdapter) Search(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(c.Response(), c.Request(), "Search", a.serveSearch) + return nil +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } res, err := a.svc.Search(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Search", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/paths/gen.go b/examples/server/echo/paths/gen.go index 90808d68..04d13d46 100644 --- a/examples/server/echo/paths/gen.go +++ b/examples/server/echo/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,301 +891,344 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(c.Response(), c.Request(), "GetName", func(w http.ResponseWriter, r *http.Request) { + a.serveGetName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetName(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetNameQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "tag", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Tag); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "tag", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.GetName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetName", res) - return nil } // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(c.Response(), c.Request(), "DeleteName", func(w http.ResponseWriter, r *http.Request) { + a.serveDeleteName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeleteName(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeleteName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, false, nil); err != nil { a.failDecode(w, r, "DeleteName", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeleteName", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.DeleteName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeleteName", res) - return nil } // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(c.Response(), c.Request(), "TraceName", func(w http.ResponseWriter, r *http.Request) { + a.serveTraceName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveTraceName(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "TraceName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.TraceName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "TraceName", res) - return nil } // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(c.Response(), c.Request(), "GetNameSlash", func(w http.ResponseWriter, r *http.Request) { + a.serveGetNameSlash(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetNameSlash(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetNameSlash", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetNameSlash(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetNameSlash", res) - return nil } // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(c.Response(), c.Request(), "GetPolicy", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPolicy(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPolicy(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPolicy", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPolicy(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPolicy", res) - return nil } // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(c.Response(), c.Request(), "GetChildren", func(w http.ResponseWriter, r *http.Request) { + a.serveGetChildren(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetChildren(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetChildren", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetChildren(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetChildren", res) - return nil } // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(c.Response(), c.Request(), "PutBatch", a.servePutBatch) + return nil +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutBatch", res) - return nil } // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(c.Response(), c.Request(), "GetGeo", func(w http.ResponseWriter, r *http.Request) { + a.serveGetGeo(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetGeo(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("lat:lng")), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetGeo", ParamName: "lat:lng", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetGeo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetGeo", res) - return nil } // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(c.Response(), c.Request(), "GetFile", func(w http.ResponseWriter, r *http.Request) { + a.serveGetFile(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetFile(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("ext")), runtime.Param{Name: "ext", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Ext); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "ext", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetFile", res) - return nil } // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(c.Response(), c.Request(), "GetProduct", func(w http.ResponseWriter, r *http.Request) { + a.serveGetProduct(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetProduct(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetProduct", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetProduct(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetProduct", res) - return nil } // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(c.Response(), c.Request(), "GetMetadata", a.serveGetMetadata) + return nil +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetMetadata", res) - return nil } // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(c.Response(), c.Request(), "GetColon", a.serveGetColon) + return nil +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetColon", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/scaffolds/gen.go b/examples/server/echo/scaffolds/gen.go index f1d4b8bb..d6c5fd5e 100644 --- a/examples/server/echo/scaffolds/gen.go +++ b/examples/server/echo/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,58 +249,63 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(c.Response(), c.Request(), "ListTodos", a.serveListTodos) + return nil +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListTodos", res) - return nil } // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(c.Response(), c.Request(), "CreateTodo", a.serveCreateTodo) + return nil +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreateTodo", err) - return nil + return } case "": a.failDecode(w, r, "CreateTodo", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreateTodo", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreateTodo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreateTodo", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echo/split/api/gen.go b/examples/server/echo/split/api/gen.go index 8e0c6d4d..44305668 100644 --- a/examples/server/echo/split/api/gen.go +++ b/examples/server/echo/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,31 +171,36 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(c.Response(), c.Request(), "GetBook", func(w http.ResponseWriter, r *http.Request) { + a.serveGetBook(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetBook(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("isbn")), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetBook", ParamName: "isbn", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetBook(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetBook", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/basic/gen.go b/examples/server/echov5/basic/gen.go index cb6508c6..f8b88fcd 100644 --- a/examples/server/echov5/basic/gen.go +++ b/examples/server/echov5/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,131 +455,146 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/basic/gen_test.go b/examples/server/echov5/basic/gen_test.go index 1ad101e6..8d0fa994 100644 --- a/examples/server/echov5/basic/gen_test.go +++ b/examples/server/echov5/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("{\"message\":\"Not Found\"}\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/echov5/bodies/gen.go b/examples/server/echov5/bodies/gen.go index e82d8137..b8565e90 100644 --- a/examples/server/echov5/bodies/gen.go +++ b/examples/server/echov5/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,271 +755,293 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(c.Response(), c.Request(), "PostJSON", a.servePostJSON) + return nil +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PostJSON", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostJSON", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostJSON(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostJSON", res) - return nil } // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(c.Response(), c.Request(), "GetForm", a.serveGetForm) + return nil +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetForm", res) - return nil } // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(c.Response(), c.Request(), "PostForm", a.servePostForm) + return nil +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, true, nil); err != nil { a.failDecode(w, r, "PostForm", err) - return nil + return } case "": a.failDecode(w, r, "PostForm", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostForm", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostForm", res) - return nil } // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(c.Response(), c.Request(), "GetQuote", a.serveGetQuote) + return nil +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetQuote", res) - return nil } // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(c.Response(), c.Request(), "GetCount", a.serveGetCount) + return nil +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetCount", res) - return nil } // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(c.Response(), c.Request(), "ListNotes", a.serveListNotes) + return nil +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListNotes", res) - return nil } // Upload handles POST /upload. func (a *HTTPAdapter) Upload(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(c.Response(), c.Request(), "Upload", a.serveUpload) + return nil +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": opts.Body = &UploadRequestBody{} if err := runtime.DecodeMultipart(r, opts.Body, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "Upload", err) - return nil + return } case "": a.failDecode(w, r, "Upload", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Upload", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.Upload(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Upload", res) - return nil } // PostText handles POST /text. func (a *HTTPAdapter) PostText(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(c.Response(), c.Request(), "PostText", a.servePostText) + return nil +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyText = runtime.Ptr(PostTextRequestBody(text)) case "application/octet-stream": data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyOctetStream = data case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostText", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostText(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostText", res) - return nil } // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(c.Response(), c.Request(), "PutFile", a.servePutFile) + return nil +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": file, err := runtime.DecodeFile(r, true) if err != nil { a.failDecode(w, r, "PutFile", err) - return nil + return } opts.Body = runtime.Ptr(PutFileRequestBody(file)) case "": a.failDecode(w, r, "PutFile", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutFile", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PutFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutFile", res) - return nil } // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(c.Response(), c.Request(), "PostAny", a.servePostAny) + return nil +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyXML = runtime.Ptr(PostAnyXMLRequestBody(text)) case "text/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyTextXML = runtime.Ptr(PostAnyXMLRequestBody2(text)) case "": @@ -1018,7 +1049,7 @@ func (a *HTTPAdapter) PostAny(c *echo.Context) error { data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyAny = data } @@ -1026,14 +1057,13 @@ func (a *HTTPAdapter) PostAny(c *echo.Context) error { res, err := a.svc.PostAny(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostAny", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/errors/gen.go b/examples/server/echov5/errors/gen.go index c1859812..9b2aaaf8 100644 --- a/examples/server/echov5/errors/gen.go +++ b/examples/server/echov5/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,28 +392,34 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetPetQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "fields", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Fields); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "fields", ParamLocation: "query", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "GetPet", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) @@ -412,50 +427,54 @@ func (a *HTTPAdapter) GetPet(c *echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(c.Response(), c.Request(), "PutPet", func(w http.ResponseWriter, r *http.Request) { + a.servePutPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePutPet(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PutPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": body, err := a.opts.Presence.JSON(r.Body, runtime.Prop{Object: "Pet"}) if err != nil { a.failBody(w, r, "PutPet", err) - return nil + return } if err = runtime.DecodeJSON(body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PutPet", err) - return nil + return } case "": a.failDecode(w, r, "PutPet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutPet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "PutPet", Err: err}) - return nil + return } res, err := a.svc.PutPet(r.Context(), opts) @@ -463,17 +482,16 @@ func (a *HTTPAdapter) PutPet(c *echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutPet", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/params/gen.go b/examples/server/echov5/params/gen.go index a5c0f416..763887f4 100644 --- a/examples/server/echov5/params/gen.go +++ b/examples/server/echov5/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,195 +688,208 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(c.Response(), c.Request(), "PathStyles", func(w http.ResponseWriter, r *http.Request) { + a.servePathStyles(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePathStyles(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("simple")), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "simple", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("label")), runtime.Param{Name: "label", Style: runtime.StyleLabel, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Label); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "label", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("matrix")), runtime.Param{Name: "matrix", Style: runtime.StyleMatrix, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Matrix); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "matrix", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("list")), runtime.Param{Name: "list", Style: runtime.StyleSimple, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.PathParams.List); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "list", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.PathStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PathStyles", res) - return nil } // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(c.Response(), c.Request(), "QueryStyles", a.serveQueryStyles) + return nil +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "form", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Form); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "form", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "csv", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Csv); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "csv", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "space", Style: runtime.StyleSpaceDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Space); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "space", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "pipe", Style: runtime.StylePipeDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Pipe); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "pipe", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "deep", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Deep); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "deep", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "flat", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Flat); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "flat", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "json", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: true}, &opts.Query.JSON); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "json", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "id", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.QueryStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "QueryStyles", res) - return nil } // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(c.Response(), c.Request(), "HeaderStyles", a.serveHeaderStyles) + return nil +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Tags", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Point", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XPoint); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Point", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-When", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XWhen); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-When", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Limit", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XLimit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Limit", ParamLocation: "header", Err: err}) - return nil + return } res, err := a.svc.HeaderStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "HeaderStyles", res) - return nil } // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(c.Response(), c.Request(), "CookieStyles", a.serveCookieStyles) + return nil +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "flags", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Cookies.Flags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "flags", ParamLocation: "cookie", Err: err}) - return nil + return } res, err := a.svc.CookieStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CookieStyles", res) - return nil } // Search handles GET /search. func (a *HTTPAdapter) Search(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(c.Response(), c.Request(), "Search", a.serveSearch) + return nil +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } res, err := a.svc.Search(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Search", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/paths/gen.go b/examples/server/echov5/paths/gen.go index 86d91fcf..4acc12c6 100644 --- a/examples/server/echov5/paths/gen.go +++ b/examples/server/echov5/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,301 +891,344 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(c.Response(), c.Request(), "GetName", func(w http.ResponseWriter, r *http.Request) { + a.serveGetName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetName(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetNameQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "tag", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Tag); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "tag", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.GetName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetName", res) - return nil } // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(c.Response(), c.Request(), "DeleteName", func(w http.ResponseWriter, r *http.Request) { + a.serveDeleteName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeleteName(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeleteName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, false, nil); err != nil { a.failDecode(w, r, "DeleteName", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeleteName", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.DeleteName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeleteName", res) - return nil } // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(c.Response(), c.Request(), "TraceName", func(w http.ResponseWriter, r *http.Request) { + a.serveTraceName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveTraceName(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "TraceName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.TraceName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "TraceName", res) - return nil } // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(c.Response(), c.Request(), "GetNameSlash", func(w http.ResponseWriter, r *http.Request) { + a.serveGetNameSlash(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetNameSlash(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetNameSlash", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetNameSlash(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetNameSlash", res) - return nil } // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(c.Response(), c.Request(), "GetPolicy", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPolicy(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPolicy(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPolicy", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPolicy(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPolicy", res) - return nil } // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(c.Response(), c.Request(), "GetChildren", func(w http.ResponseWriter, r *http.Request) { + a.serveGetChildren(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetChildren(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetChildren", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetChildren(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetChildren", res) - return nil } // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(c.Response(), c.Request(), "PutBatch", a.servePutBatch) + return nil +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutBatch", res) - return nil } // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(c.Response(), c.Request(), "GetGeo", func(w http.ResponseWriter, r *http.Request) { + a.serveGetGeo(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetGeo(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("lat:lng")), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetGeo", ParamName: "lat:lng", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetGeo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetGeo", res) - return nil } // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(c.Response(), c.Request(), "GetFile", func(w http.ResponseWriter, r *http.Request) { + a.serveGetFile(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetFile(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("name")), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("ext")), runtime.Param{Name: "ext", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Ext); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "ext", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetFile", res) - return nil } // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(c.Response(), c.Request(), "GetProduct", func(w http.ResponseWriter, r *http.Request) { + a.serveGetProduct(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetProduct(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetProduct", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetProduct(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetProduct", res) - return nil } // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(c.Response(), c.Request(), "GetMetadata", a.serveGetMetadata) + return nil +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetMetadata", res) - return nil } // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(c.Response(), c.Request(), "GetColon", a.serveGetColon) + return nil +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetColon", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/scaffolds/gen.go b/examples/server/echov5/scaffolds/gen.go index 99b4800c..8d0b29f0 100644 --- a/examples/server/echov5/scaffolds/gen.go +++ b/examples/server/echov5/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,58 +249,63 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(c.Response(), c.Request(), "ListTodos", a.serveListTodos) + return nil +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListTodos", res) - return nil } // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(c.Response(), c.Request(), "CreateTodo", a.serveCreateTodo) + return nil +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreateTodo", err) - return nil + return } case "": a.failDecode(w, r, "CreateTodo", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreateTodo", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreateTodo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreateTodo", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/echov5/split/api/gen.go b/examples/server/echov5/split/api/gen.go index e20e53b3..5fdff3ca 100644 --- a/examples/server/echov5/split/api/gen.go +++ b/examples/server/echov5/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,31 +171,36 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(c.Response(), c.Request(), "GetBook", func(w http.ResponseWriter, r *http.Request) { + a.serveGetBook(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetBook(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("isbn")), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetBook", ParamName: "isbn", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetBook(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetBook", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/fasthttp/basic/gen.go b/examples/server/fasthttp/basic/gen.go index 14d99211..0418fa30 100644 --- a/examples/server/fasthttp/basic/gen.go +++ b/examples/server/fasthttp/basic/gen.go @@ -373,12 +373,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -405,6 +406,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -435,8 +443,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -448,12 +457,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -476,7 +489,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -506,7 +522,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -528,7 +547,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -550,7 +572,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/fasthttp/basic/gen_test.go b/examples/server/fasthttp/basic/gen_test.go index 0e6d9f85..ae060bfb 100644 --- a/examples/server/fasthttp/basic/gen_test.go +++ b/examples/server/fasthttp/basic/gen_test.go @@ -72,6 +72,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, fasthttptest.Handler((NewRouter(&service{pets: map[int]Pet{}})).Handler), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return fasthttptest.Handler((NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw))).Handler) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/fasthttp/bodies/gen.go b/examples/server/fasthttp/bodies/gen.go index 5725e6bc..ec45ee85 100644 --- a/examples/server/fasthttp/bodies/gen.go +++ b/examples/server/fasthttp/bodies/gen.go @@ -673,12 +673,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -705,6 +706,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -735,8 +743,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -748,12 +757,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -781,7 +794,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -798,7 +814,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -828,7 +847,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -845,7 +867,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -862,7 +887,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -879,7 +907,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -910,7 +941,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -947,7 +981,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -979,7 +1016,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/fasthttp/errors/gen.go b/examples/server/fasthttp/errors/gen.go index 04101a6b..984cbd14 100644 --- a/examples/server/fasthttp/errors/gen.go +++ b/examples/server/fasthttp/errors/gen.go @@ -301,13 +301,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -335,6 +336,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -372,8 +380,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -385,12 +394,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -427,7 +440,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/fasthttp/params/gen.go b/examples/server/fasthttp/params/gen.go index f85b4b3a..586a5660 100644 --- a/examples/server/fasthttp/params/gen.go +++ b/examples/server/fasthttp/params/gen.go @@ -606,12 +606,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -638,6 +639,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -668,8 +676,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -681,12 +690,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -720,7 +733,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -783,7 +799,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -817,7 +836,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -843,7 +865,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/fasthttp/paths/gen.go b/examples/server/fasthttp/paths/gen.go index 44dfab9d..2fd1cf5d 100644 --- a/examples/server/fasthttp/paths/gen.go +++ b/examples/server/fasthttp/paths/gen.go @@ -809,12 +809,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -841,6 +842,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -871,8 +879,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -884,12 +893,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -917,7 +930,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -950,7 +966,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -972,7 +991,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -994,7 +1016,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1016,7 +1041,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1038,7 +1066,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1055,7 +1086,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1077,7 +1111,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1103,7 +1140,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1125,7 +1165,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1142,7 +1185,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/fasthttp/scaffolds/gen.go b/examples/server/fasthttp/scaffolds/gen.go index d79e7916..5cf10375 100644 --- a/examples/server/fasthttp/scaffolds/gen.go +++ b/examples/server/fasthttp/scaffolds/gen.go @@ -167,12 +167,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -199,6 +200,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -229,8 +237,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -242,12 +251,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -264,7 +277,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/fasthttp/split/api/gen.go b/examples/server/fasthttp/split/api/gen.go index aa8d7019..25155dc4 100644 --- a/examples/server/fasthttp/split/api/gen.go +++ b/examples/server/fasthttp/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/fiber/basic/gen.go b/examples/server/fiber/basic/gen.go index ca615d1c..26989903 100644 --- a/examples/server/fiber/basic/gen.go +++ b/examples/server/fiber/basic/gen.go @@ -373,12 +373,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -405,6 +406,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -435,8 +443,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -448,12 +457,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -476,7 +489,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -506,7 +522,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -528,7 +547,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -550,7 +572,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/fiber/basic/gen_test.go b/examples/server/fiber/basic/gen_test.go index b48021b0..f8929239 100644 --- a/examples/server/fiber/basic/gen_test.go +++ b/examples/server/fiber/basic/gen_test.go @@ -71,6 +71,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, adaptor.FiberApp(NewRouter(&service{pets: map[int]Pet{}})), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return adaptor.FiberApp(NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw))) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/fiber/bodies/gen.go b/examples/server/fiber/bodies/gen.go index c79b0f09..06d172f7 100644 --- a/examples/server/fiber/bodies/gen.go +++ b/examples/server/fiber/bodies/gen.go @@ -673,12 +673,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -705,6 +706,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -735,8 +743,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -748,12 +757,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -781,7 +794,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -798,7 +814,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -828,7 +847,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -845,7 +867,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -862,7 +887,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -879,7 +907,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -910,7 +941,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -947,7 +981,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -979,7 +1016,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/fiber/errors/gen.go b/examples/server/fiber/errors/gen.go index e2bd4563..540faffc 100644 --- a/examples/server/fiber/errors/gen.go +++ b/examples/server/fiber/errors/gen.go @@ -301,13 +301,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -335,6 +336,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -372,8 +380,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -385,12 +394,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -427,7 +440,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/fiber/params/gen.go b/examples/server/fiber/params/gen.go index af068e4e..50f2207e 100644 --- a/examples/server/fiber/params/gen.go +++ b/examples/server/fiber/params/gen.go @@ -606,12 +606,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -638,6 +639,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -668,8 +676,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -681,12 +690,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -720,7 +733,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -783,7 +799,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -817,7 +836,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -843,7 +865,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/fiber/paths/gen.go b/examples/server/fiber/paths/gen.go index 1a432acf..8b5d4d4b 100644 --- a/examples/server/fiber/paths/gen.go +++ b/examples/server/fiber/paths/gen.go @@ -809,12 +809,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -841,6 +842,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -871,8 +879,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -884,12 +893,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -917,7 +930,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -950,7 +966,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -972,7 +991,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -994,7 +1016,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1016,7 +1041,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1038,7 +1066,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1055,7 +1086,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1077,7 +1111,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1103,7 +1140,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1125,7 +1165,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1142,7 +1185,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/fiber/scaffolds/gen.go b/examples/server/fiber/scaffolds/gen.go index 465bcca9..d50105b9 100644 --- a/examples/server/fiber/scaffolds/gen.go +++ b/examples/server/fiber/scaffolds/gen.go @@ -167,12 +167,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -199,6 +200,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -229,8 +237,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -242,12 +251,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -264,7 +277,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/fiber/split/api/gen.go b/examples/server/fiber/split/api/gen.go index f4c827a9..798a04cf 100644 --- a/examples/server/fiber/split/api/gen.go +++ b/examples/server/fiber/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/gin/basic/gen.go b/examples/server/gin/basic/gen.go index 7632a6de..7f4757f0 100644 --- a/examples/server/gin/basic/gen.go +++ b/examples/server/gin/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,12 +455,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -474,7 +487,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -504,7 +520,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -526,7 +545,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -548,7 +570,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/gin/basic/gen_test.go b/examples/server/gin/basic/gen_test.go index 697e69ec..289c4dac 100644 --- a/examples/server/gin/basic/gen_test.go +++ b/examples/server/gin/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/gin/bodies/gen.go b/examples/server/gin/bodies/gen.go index e85f9f4a..6547d098 100644 --- a/examples/server/gin/bodies/gen.go +++ b/examples/server/gin/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,12 +755,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -779,7 +792,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -796,7 +812,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -826,7 +845,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -843,7 +865,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -860,7 +885,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -877,7 +905,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -908,7 +939,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -945,7 +979,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -977,7 +1014,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/gin/errors/gen.go b/examples/server/gin/errors/gen.go index ab5fe795..09a083ad 100644 --- a/examples/server/gin/errors/gen.go +++ b/examples/server/gin/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,12 +392,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -425,7 +438,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/gin/params/gen.go b/examples/server/gin/params/gen.go index 42887143..04967ec7 100644 --- a/examples/server/gin/params/gen.go +++ b/examples/server/gin/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,12 +688,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -718,7 +731,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -781,7 +797,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -815,7 +834,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -841,7 +863,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/gin/paths/gen.go b/examples/server/gin/paths/gen.go index b3e973ef..07cfece4 100644 --- a/examples/server/gin/paths/gen.go +++ b/examples/server/gin/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,12 +891,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -915,7 +928,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -948,7 +964,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -970,7 +989,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -992,7 +1014,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1014,7 +1039,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1036,7 +1064,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1053,7 +1084,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat:lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1075,7 +1109,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1101,7 +1138,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1123,7 +1163,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1140,7 +1183,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/gin/scaffolds/gen.go b/examples/server/gin/scaffolds/gen.go index fabb3cf8..e8e75cd2 100644 --- a/examples/server/gin/scaffolds/gen.go +++ b/examples/server/gin/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -262,7 +275,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/gin/split/api/gen.go b/examples/server/gin/split/api/gen.go index aef52180..938ba76f 100644 --- a/examples/server/gin/split/api/gen.go +++ b/examples/server/gin/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/goframe/basic/gen.go b/examples/server/goframe/basic/gen.go index 3251b965..13c65dae 100644 --- a/examples/server/goframe/basic/gen.go +++ b/examples/server/goframe/basic/gen.go @@ -374,12 +374,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -406,6 +407,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -436,8 +444,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -449,12 +458,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -477,7 +490,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -507,7 +523,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -529,7 +548,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -551,7 +573,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/goframe/basic/gen_test.go b/examples/server/goframe/basic/gen_test.go index 667a26bb..ad139f7f 100644 --- a/examples/server/goframe/basic/gen_test.go +++ b/examples/server/goframe/basic/gen_test.go @@ -71,6 +71,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, goframetest.Handler(t, NewRouter(&service{pets: map[int]Pet{}})), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return goframetest.Handler(t, NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw))) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/goframe/bodies/gen.go b/examples/server/goframe/bodies/gen.go index 2da901ce..2dac093c 100644 --- a/examples/server/goframe/bodies/gen.go +++ b/examples/server/goframe/bodies/gen.go @@ -674,12 +674,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -706,6 +707,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -736,8 +744,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -749,12 +758,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -782,7 +795,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -799,7 +815,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -829,7 +848,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -846,7 +868,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -863,7 +888,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -880,7 +908,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -911,7 +942,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -948,7 +982,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -980,7 +1017,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/goframe/errors/gen.go b/examples/server/goframe/errors/gen.go index 27e20f16..237d59d8 100644 --- a/examples/server/goframe/errors/gen.go +++ b/examples/server/goframe/errors/gen.go @@ -302,13 +302,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -336,6 +337,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -373,8 +381,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -386,12 +395,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -428,7 +441,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/goframe/params/gen.go b/examples/server/goframe/params/gen.go index c7c44206..7f4e2908 100644 --- a/examples/server/goframe/params/gen.go +++ b/examples/server/goframe/params/gen.go @@ -607,12 +607,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -639,6 +640,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -669,8 +677,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -682,12 +691,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -721,7 +734,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -784,7 +800,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -818,7 +837,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -844,7 +866,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/goframe/paths/gen.go b/examples/server/goframe/paths/gen.go index 1b689fe7..6c892ae3 100644 --- a/examples/server/goframe/paths/gen.go +++ b/examples/server/goframe/paths/gen.go @@ -810,12 +810,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -842,6 +843,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -872,8 +880,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -885,12 +894,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -918,7 +931,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -951,7 +967,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -973,7 +992,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -995,7 +1017,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1017,7 +1042,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1039,7 +1067,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1056,7 +1087,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1078,7 +1112,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1104,7 +1141,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1126,7 +1166,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1143,7 +1186,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/goframe/scaffolds/gen.go b/examples/server/goframe/scaffolds/gen.go index b731c77f..266e365c 100644 --- a/examples/server/goframe/scaffolds/gen.go +++ b/examples/server/goframe/scaffolds/gen.go @@ -168,12 +168,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -200,6 +201,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -230,8 +238,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -243,12 +252,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -265,7 +278,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/goframe/split/api/gen.go b/examples/server/goframe/split/api/gen.go index de913e1c..22187f64 100644 --- a/examples/server/goframe/split/api/gen.go +++ b/examples/server/goframe/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/gorillamux/basic/gen.go b/examples/server/gorillamux/basic/gen.go index b9736fd0..8cb77d56 100644 --- a/examples/server/gorillamux/basic/gen.go +++ b/examples/server/gorillamux/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,12 +455,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -474,7 +487,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -504,7 +520,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(mux.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -526,7 +545,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(mux.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -548,7 +570,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/gorillamux/basic/gen_test.go b/examples/server/gorillamux/basic/gen_test.go index 8209995d..16ee40c8 100644 --- a/examples/server/gorillamux/basic/gen_test.go +++ b/examples/server/gorillamux/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/gorillamux/bodies/gen.go b/examples/server/gorillamux/bodies/gen.go index 72baeb2f..3eb1a22a 100644 --- a/examples/server/gorillamux/bodies/gen.go +++ b/examples/server/gorillamux/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,12 +755,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -779,7 +792,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -796,7 +812,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -826,7 +845,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -843,7 +865,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -860,7 +885,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -877,7 +905,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -908,7 +939,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -945,7 +979,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -977,7 +1014,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/gorillamux/errors/gen.go b/examples/server/gorillamux/errors/gen.go index 6db0a7e1..95c30bcc 100644 --- a/examples/server/gorillamux/errors/gen.go +++ b/examples/server/gorillamux/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,12 +392,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -425,7 +438,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(mux.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/gorillamux/params/gen.go b/examples/server/gorillamux/params/gen.go index f7fabccf..501e89e3 100644 --- a/examples/server/gorillamux/params/gen.go +++ b/examples/server/gorillamux/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,12 +688,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(mux.Vars(r)["simple"], runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -718,7 +731,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -781,7 +797,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -815,7 +834,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -841,7 +863,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/gorillamux/paths/gen.go b/examples/server/gorillamux/paths/gen.go index ec01753f..b4648afb 100644 --- a/examples/server/gorillamux/paths/gen.go +++ b/examples/server/gorillamux/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,12 +891,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -915,7 +928,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(mux.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -948,7 +964,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(mux.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -970,7 +989,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(mux.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -992,7 +1014,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(mux.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1014,7 +1039,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(mux.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1036,7 +1064,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1053,7 +1084,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(mux.Vars(r)["lat_lng"], runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1075,7 +1109,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(mux.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1101,7 +1138,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(mux.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1123,7 +1163,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1140,7 +1183,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/gorillamux/scaffolds/gen.go b/examples/server/gorillamux/scaffolds/gen.go index 5be9bbe8..cae05eb8 100644 --- a/examples/server/gorillamux/scaffolds/gen.go +++ b/examples/server/gorillamux/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -262,7 +275,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/gorillamux/split/api/gen.go b/examples/server/gorillamux/split/api/gen.go index dd045846..07bfb448 100644 --- a/examples/server/gorillamux/split/api/gen.go +++ b/examples/server/gorillamux/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,12 +171,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(mux.Vars(r)["isbn"], runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/gozero/basic/gen.go b/examples/server/gozero/basic/gen.go index 22b69d9c..92cb73a6 100644 --- a/examples/server/gozero/basic/gen.go +++ b/examples/server/gozero/basic/gen.go @@ -373,12 +373,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -405,6 +406,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -435,8 +443,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -448,12 +457,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -476,7 +489,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -506,7 +522,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -528,7 +547,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -550,7 +572,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/gozero/basic/gen_test.go b/examples/server/gozero/basic/gen_test.go index 1860921c..966841ad 100644 --- a/examples/server/gozero/basic/gen_test.go +++ b/examples/server/gozero/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/gozero/bodies/gen.go b/examples/server/gozero/bodies/gen.go index e94517ec..dfd42b18 100644 --- a/examples/server/gozero/bodies/gen.go +++ b/examples/server/gozero/bodies/gen.go @@ -672,12 +672,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -704,6 +705,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -734,8 +742,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -747,12 +756,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -780,7 +793,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -797,7 +813,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -827,7 +846,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -844,7 +866,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -861,7 +886,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -878,7 +906,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -909,7 +940,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -946,7 +980,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -978,7 +1015,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/gozero/errors/gen.go b/examples/server/gozero/errors/gen.go index afb5cb98..7eeadbed 100644 --- a/examples/server/gozero/errors/gen.go +++ b/examples/server/gozero/errors/gen.go @@ -301,13 +301,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -335,6 +336,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -372,8 +380,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -385,12 +394,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -427,7 +440,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/gozero/params/gen.go b/examples/server/gozero/params/gen.go index 7138fa54..801a182e 100644 --- a/examples/server/gozero/params/gen.go +++ b/examples/server/gozero/params/gen.go @@ -606,12 +606,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -638,6 +639,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -668,8 +676,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -681,12 +690,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["simple"], runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -720,7 +733,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -783,7 +799,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -817,7 +836,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -843,7 +865,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/gozero/paths/gen.go b/examples/server/gozero/paths/gen.go index 954e658a..ab4b7472 100644 --- a/examples/server/gozero/paths/gen.go +++ b/examples/server/gozero/paths/gen.go @@ -809,12 +809,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -841,6 +842,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -871,8 +879,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -884,12 +893,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -917,7 +930,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -950,7 +966,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -972,7 +991,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -994,7 +1016,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1016,7 +1041,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1038,7 +1066,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1055,7 +1086,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["lat:lng"], runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1077,7 +1111,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["name"], runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1103,7 +1140,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["id"], runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1125,7 +1165,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1142,7 +1185,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/gozero/scaffolds/gen.go b/examples/server/gozero/scaffolds/gen.go index 9bab056f..300769c9 100644 --- a/examples/server/gozero/scaffolds/gen.go +++ b/examples/server/gozero/scaffolds/gen.go @@ -166,12 +166,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -198,6 +199,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -228,8 +236,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -241,12 +250,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -263,7 +276,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/gozero/split/api/gen.go b/examples/server/gozero/split/api/gen.go index e3446643..add18b1b 100644 --- a/examples/server/gozero/split/api/gen.go +++ b/examples/server/gozero/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,12 +171,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(pathvar.Vars(r)["isbn"], runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/hertz/basic/gen.go b/examples/server/hertz/basic/gen.go index 0a93ba09..14865184 100644 --- a/examples/server/hertz/basic/gen.go +++ b/examples/server/hertz/basic/gen.go @@ -375,12 +375,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -407,6 +408,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -437,8 +445,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -450,12 +459,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -478,7 +491,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -508,7 +524,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -530,7 +549,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -552,7 +574,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/hertz/basic/gen_test.go b/examples/server/hertz/basic/gen_test.go index 269e87ef..c2f31299 100644 --- a/examples/server/hertz/basic/gen_test.go +++ b/examples/server/hertz/basic/gen_test.go @@ -71,6 +71,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, hertztest.Handler((NewRouter(&service{pets: map[int]Pet{}})).Engine), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return hertztest.Handler((NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw))).Engine) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/hertz/bodies/gen.go b/examples/server/hertz/bodies/gen.go index ca8b98dd..418abed4 100644 --- a/examples/server/hertz/bodies/gen.go +++ b/examples/server/hertz/bodies/gen.go @@ -675,12 +675,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -707,6 +708,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -737,8 +745,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -750,12 +759,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -783,7 +796,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -800,7 +816,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -830,7 +849,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -847,7 +869,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -864,7 +889,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -881,7 +909,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -912,7 +943,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -949,7 +983,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -981,7 +1018,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/hertz/errors/gen.go b/examples/server/hertz/errors/gen.go index dd6f4c99..7ec30e37 100644 --- a/examples/server/hertz/errors/gen.go +++ b/examples/server/hertz/errors/gen.go @@ -303,13 +303,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -337,6 +338,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -374,8 +382,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -387,12 +396,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -429,7 +442,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/hertz/params/gen.go b/examples/server/hertz/params/gen.go index e5482643..178cb3ec 100644 --- a/examples/server/hertz/params/gen.go +++ b/examples/server/hertz/params/gen.go @@ -608,12 +608,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -640,6 +641,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -670,8 +678,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -683,12 +692,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -722,7 +735,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -785,7 +801,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -819,7 +838,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -845,7 +867,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/hertz/paths/gen.go b/examples/server/hertz/paths/gen.go index 5b681dd5..ea624ab5 100644 --- a/examples/server/hertz/paths/gen.go +++ b/examples/server/hertz/paths/gen.go @@ -811,12 +811,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -843,6 +844,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -873,8 +881,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -886,12 +895,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -919,7 +932,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -952,7 +968,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -974,7 +993,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -996,7 +1018,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1018,7 +1043,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1040,7 +1068,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1057,7 +1088,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1079,7 +1113,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1105,7 +1142,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1127,7 +1167,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1144,7 +1187,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/hertz/scaffolds/gen.go b/examples/server/hertz/scaffolds/gen.go index d768a691..fb5e8767 100644 --- a/examples/server/hertz/scaffolds/gen.go +++ b/examples/server/hertz/scaffolds/gen.go @@ -169,12 +169,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -201,6 +202,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -231,8 +239,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -244,12 +253,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -266,7 +279,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/hertz/split/api/gen.go b/examples/server/hertz/split/api/gen.go index dca4c0cb..1ff682a8 100644 --- a/examples/server/hertz/split/api/gen.go +++ b/examples/server/hertz/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/internal/servertest/servertest.go b/examples/server/internal/servertest/servertest.go index 16bbb32f..240e6939 100644 --- a/examples/server/internal/servertest/servertest.go +++ b/examples/server/internal/servertest/servertest.go @@ -15,11 +15,14 @@ import ( "net" "net/http" "net/http/httptest" + "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/mockzilla/mockzilla-codegen/pkg/runtime" ) // Request is one request and the response it must get. Method is GET and WantStatus 200 when @@ -300,3 +303,26 @@ func Multipart(t *testing.T, h http.Handler) { assert.Equal(t, http.StatusOK, rec.Code) assert.JSONEq(t, `{"title":"Cat","file":"meow","name":"cat.txt","tags":["a","b"],"meta":{"text":"inner"}}`, rec.Body.String()) } + +// OperationMiddleware checks that the middleware newRouter passes to WithOperationMiddleware is +// built once and wraps each operation of the basic example, a request that fails to parse too. +func OperationMiddleware(t *testing.T, newRouter func(mw func(http.Handler) http.Handler) http.Handler) { + t.Helper() + + var built int + var seen []string + h := newRouter(func(next http.Handler) http.Handler { + built++ + return http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + rec := httptest.NewRecorder() + next.ServeHTTP(rec, r) + seen = append(seen, runtime.OperationID(r.Context())+" "+strconv.Itoa(rec.Code)) + }) + }) + for _, path := range []string{"/ping", "/pets/x", "/ping"} { + h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, path, nil)) + } + + assert.Equal(t, 1, built, "the middleware is built once") + assert.Equal(t, []string{"Ping 200", "GetPet 400", "Ping 200"}, seen) +} diff --git a/examples/server/iris/basic/gen.go b/examples/server/iris/basic/gen.go index e26710a4..9c210e84 100644 --- a/examples/server/iris/basic/gen.go +++ b/examples/server/iris/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,12 +455,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -474,7 +487,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -504,7 +520,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -526,7 +545,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -548,7 +570,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/iris/basic/gen_test.go b/examples/server/iris/basic/gen_test.go index 0a041171..77788033 100644 --- a/examples/server/iris/basic/gen_test.go +++ b/examples/server/iris/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/iris/bodies/gen.go b/examples/server/iris/bodies/gen.go index e6719bc0..0a2252e0 100644 --- a/examples/server/iris/bodies/gen.go +++ b/examples/server/iris/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,12 +755,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -779,7 +792,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -796,7 +812,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -826,7 +845,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -843,7 +865,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -860,7 +885,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -877,7 +905,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -908,7 +939,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -945,7 +979,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -977,7 +1014,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/iris/errors/gen.go b/examples/server/iris/errors/gen.go index 905a2166..6819a5aa 100644 --- a/examples/server/iris/errors/gen.go +++ b/examples/server/iris/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,12 +392,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -425,7 +438,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/iris/params/gen.go b/examples/server/iris/params/gen.go index dec87a0b..333ea663 100644 --- a/examples/server/iris/params/gen.go +++ b/examples/server/iris/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,12 +688,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -718,7 +731,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -781,7 +797,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -815,7 +834,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -841,7 +863,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/iris/paths/gen.go b/examples/server/iris/paths/gen.go index 1ef1f373..1c42bb95 100644 --- a/examples/server/iris/paths/gen.go +++ b/examples/server/iris/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,12 +891,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -915,7 +928,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -948,7 +964,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -970,7 +989,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -992,7 +1014,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1014,7 +1039,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1036,7 +1064,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1053,7 +1084,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1075,7 +1109,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1101,7 +1138,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1123,7 +1163,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1140,7 +1183,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/iris/scaffolds/gen.go b/examples/server/iris/scaffolds/gen.go index 9eb353c0..b2245a12 100644 --- a/examples/server/iris/scaffolds/gen.go +++ b/examples/server/iris/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -262,7 +275,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/iris/split/api/gen.go b/examples/server/iris/split/api/gen.go index 05d8a0bf..175d079c 100644 --- a/examples/server/iris/split/api/gen.go +++ b/examples/server/iris/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/server/kratos/basic/gen.go b/examples/server/kratos/basic/gen.go index 8d686e99..a620306b 100644 --- a/examples/server/kratos/basic/gen.go +++ b/examples/server/kratos/basic/gen.go @@ -371,12 +371,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -403,6 +404,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -433,8 +441,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -446,131 +455,146 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/basic/gen_test.go b/examples/server/kratos/basic/gen_test.go index 7e05c047..769a43d4 100644 --- a/examples/server/kratos/basic/gen_test.go +++ b/examples/server/kratos/basic/gen_test.go @@ -69,6 +69,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/kratos/bodies/gen.go b/examples/server/kratos/bodies/gen.go index 772b8577..2260165a 100644 --- a/examples/server/kratos/bodies/gen.go +++ b/examples/server/kratos/bodies/gen.go @@ -671,12 +671,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -703,6 +704,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -733,8 +741,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -746,271 +755,293 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(c.Response(), c.Request(), "PostJSON", a.servePostJSON) + return nil +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PostJSON", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostJSON", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostJSON(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostJSON", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostJSON", res) - return nil } // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(c.Response(), c.Request(), "GetForm", a.serveGetForm) + return nil +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetForm", res) - return nil } // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(c.Response(), c.Request(), "PostForm", a.servePostForm) + return nil +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, true, nil); err != nil { a.failDecode(w, r, "PostForm", err) - return nil + return } case "": a.failDecode(w, r, "PostForm", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostForm", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostForm(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostForm", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostForm", res) - return nil } // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(c.Response(), c.Request(), "GetQuote", a.serveGetQuote) + return nil +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetQuote", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetQuote", res) - return nil } // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(c.Response(), c.Request(), "GetCount", a.serveGetCount) + return nil +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetCount", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetCount", res) - return nil } // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(c.Response(), c.Request(), "ListNotes", a.serveListNotes) + return nil +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListNotes", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListNotes", res) - return nil } // Upload handles POST /upload. func (a *HTTPAdapter) Upload(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(c.Response(), c.Request(), "Upload", a.serveUpload) + return nil +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": opts.Body = &UploadRequestBody{} if err := runtime.DecodeMultipart(r, opts.Body, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "Upload", err) - return nil + return } case "": a.failDecode(w, r, "Upload", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Upload", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.Upload(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Upload", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Upload", res) - return nil } // PostText handles POST /text. func (a *HTTPAdapter) PostText(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(c.Response(), c.Request(), "PostText", a.servePostText) + return nil +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyText = runtime.Ptr(PostTextRequestBody(text)) case "application/octet-stream": data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostText", err) - return nil + return } opts.BodyOctetStream = data case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PostText", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PostText(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostText", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostText", res) - return nil } // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(c.Response(), c.Request(), "PutFile", a.servePutFile) + return nil +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": file, err := runtime.DecodeFile(r, true) if err != nil { a.failDecode(w, r, "PutFile", err) - return nil + return } opts.Body = runtime.Ptr(PutFileRequestBody(file)) case "": a.failDecode(w, r, "PutFile", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutFile", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.PutFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutFile", res) - return nil } // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(c.Response(), c.Request(), "PostAny", a.servePostAny) + return nil +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyXML = runtime.Ptr(PostAnyXMLRequestBody(text)) case "text/xml": text, err := runtime.DecodeText(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyTextXML = runtime.Ptr(PostAnyXMLRequestBody2(text)) case "": @@ -1018,7 +1049,7 @@ func (a *HTTPAdapter) PostAny(c khttp.Context) error { data, err := runtime.DecodeBytes(r.Body, false) if err != nil { a.failDecode(w, r, "PostAny", err) - return nil + return } opts.BodyAny = data } @@ -1026,14 +1057,13 @@ func (a *HTTPAdapter) PostAny(c khttp.Context) error { res, err := a.svc.PostAny(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PostAny", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PostAny", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/errors/gen.go b/examples/server/kratos/errors/gen.go index 5e74259b..3d34863a 100644 --- a/examples/server/kratos/errors/gen.go +++ b/examples/server/kratos/errors/gen.go @@ -299,13 +299,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -333,6 +334,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -370,8 +378,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -383,28 +392,34 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(c.Response(), c.Request(), "GetPet", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPet(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetPetQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "fields", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Fields); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPet", ParamName: "fields", ParamLocation: "query", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "GetPet", Err: err}) - return nil + return } res, err := a.svc.GetPet(r.Context(), opts) @@ -412,50 +427,54 @@ func (a *HTTPAdapter) GetPet(c khttp.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPet", res) - return nil } // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(c.Response(), c.Request(), "PutPet", func(w http.ResponseWriter, r *http.Request) { + a.servePutPet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePutPet(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PutPet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": body, err := a.opts.Presence.JSON(r.Body, runtime.Prop{Object: "Pet"}) if err != nil { a.failBody(w, r, "PutPet", err) - return nil + return } if err = runtime.DecodeJSON(body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "PutPet", err) - return nil + return } case "": a.failDecode(w, r, "PutPet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "PutPet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "PutPet", Err: err}) - return nil + return } res, err := a.svc.PutPet(r.Context(), opts) @@ -463,17 +482,16 @@ func (a *HTTPAdapter) PutPet(c khttp.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutPet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutPet", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/params/gen.go b/examples/server/kratos/params/gen.go index 38d90cab..6c44dfdf 100644 --- a/examples/server/kratos/params/gen.go +++ b/examples/server/kratos/params/gen.go @@ -604,12 +604,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -636,6 +637,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -666,8 +674,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -679,195 +688,208 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(c.Response(), c.Request(), "PathStyles", func(w http.ResponseWriter, r *http.Request) { + a.servePathStyles(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) servePathStyles(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(c.Vars().Get("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "simple", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(c.Vars().Get("label"), runtime.Param{Name: "label", Style: runtime.StyleLabel, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Label); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "label", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(c.Vars().Get("matrix"), runtime.Param{Name: "matrix", Style: runtime.StyleMatrix, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Matrix); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "matrix", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(c.Vars().Get("list"), runtime.Param{Name: "list", Style: runtime.StyleSimple, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.PathParams.List); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "PathStyles", ParamName: "list", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.PathStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PathStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PathStyles", res) - return nil } // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(c.Response(), c.Request(), "QueryStyles", a.serveQueryStyles) + return nil +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "form", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Form); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "form", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "csv", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Csv); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "csv", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "space", Style: runtime.StyleSpaceDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Space); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "space", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "pipe", Style: runtime.StylePipeDelimited, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Pipe); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "pipe", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "deep", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Deep); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "deep", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "flat", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Flat); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "flat", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "json", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: true}, &opts.Query.JSON); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "json", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "id", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "id", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "created", Style: runtime.StyleDeepObject, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Query.Created); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "created", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "needed", Style: runtime.StyleForm, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Needed); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "needed", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "QueryStyles", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.QueryStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "QueryStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "QueryStyles", res) - return nil } // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(c.Response(), c.Request(), "HeaderStyles", a.serveHeaderStyles) + return nil +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Tags", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Point", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XPoint); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Point", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-When", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XWhen); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-When", ParamLocation: "header", Err: err}) - return nil + return } if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Limit", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XLimit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "HeaderStyles", ParamName: "X-Limit", ParamLocation: "header", Err: err}) - return nil + return } res, err := a.svc.HeaderStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "HeaderStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "HeaderStyles", res) - return nil } // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(c.Response(), c.Request(), "CookieStyles", a.serveCookieStyles) + return nil +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "flags", Style: runtime.StyleForm, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Cookies.Flags); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "CookieStyles", ParamName: "flags", ParamLocation: "cookie", Err: err}) - return nil + return } res, err := a.svc.CookieStyles(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CookieStyles", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CookieStyles", res) - return nil } // Search handles GET /search. func (a *HTTPAdapter) Search(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(c.Response(), c.Request(), "Search", a.serveSearch) + return nil +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } res, err := a.svc.Search(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Search", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Search", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/paths/gen.go b/examples/server/kratos/paths/gen.go index 0b350b4a..6a07a91e 100644 --- a/examples/server/kratos/paths/gen.go +++ b/examples/server/kratos/paths/gen.go @@ -807,12 +807,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -839,6 +840,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -869,8 +877,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -882,301 +891,344 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(c.Response(), c.Request(), "GetName", func(w http.ResponseWriter, r *http.Request) { + a.serveGetName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetName(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } opts.Query = &GetNameQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "tag", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Query.Tag); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetName", ParamName: "tag", ParamLocation: "query", Err: err}) - return nil + return } res, err := a.svc.GetName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetName", res) - return nil } // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(c.Response(), c.Request(), "DeleteName", func(w http.ResponseWriter, r *http.Request) { + a.serveDeleteName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeleteName(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeleteName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.Body, false, nil); err != nil { a.failDecode(w, r, "DeleteName", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeleteName", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.DeleteName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeleteName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeleteName", res) - return nil } // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(c.Response(), c.Request(), "TraceName", func(w http.ResponseWriter, r *http.Request) { + a.serveTraceName(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveTraceName(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "TraceName", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.TraceName(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "TraceName", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "TraceName", res) - return nil } // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(c.Response(), c.Request(), "GetNameSlash", func(w http.ResponseWriter, r *http.Request) { + a.serveGetNameSlash(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetNameSlash(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetNameSlash", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetNameSlash(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetNameSlash", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetNameSlash", res) - return nil } // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(c.Response(), c.Request(), "GetPolicy", func(w http.ResponseWriter, r *http.Request) { + a.serveGetPolicy(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetPolicy(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetPolicy", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetPolicy(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetPolicy", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetPolicy", res) - return nil } // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(c.Response(), c.Request(), "GetChildren", func(w http.ResponseWriter, r *http.Request) { + a.serveGetChildren(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetChildren(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetChildren", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetChildren(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetChildren", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetChildren", res) - return nil } // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(c.Response(), c.Request(), "PutBatch", a.servePutBatch) + return nil +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "PutBatch", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "PutBatch", res) - return nil } // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(c.Response(), c.Request(), "GetGeo", func(w http.ResponseWriter, r *http.Request) { + a.serveGetGeo(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetGeo(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(c.Vars().Get("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetGeo", ParamName: "lat:lng", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetGeo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetGeo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetGeo", res) - return nil } // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(c.Response(), c.Request(), "GetFile", func(w http.ResponseWriter, r *http.Request) { + a.serveGetFile(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetFile(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(c.Vars().Get("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "name", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodePath(c.Vars().Get("ext"), runtime.Param{Name: "ext", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Ext); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetFile", ParamName: "ext", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetFile(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetFile", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetFile", res) - return nil } // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(c.Response(), c.Request(), "GetProduct", func(w http.ResponseWriter, r *http.Request) { + a.serveGetProduct(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetProduct(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetProduct", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetProduct(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetProduct", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetProduct", res) - return nil } // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(c.Response(), c.Request(), "GetMetadata", a.serveGetMetadata) + return nil +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetMetadata", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetMetadata", res) - return nil } // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(c.Response(), c.Request(), "GetColon", a.serveGetColon) + return nil +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetColon", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetColon", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/scaffolds/gen.go b/examples/server/kratos/scaffolds/gen.go index a8ae5d0a..67567364 100644 --- a/examples/server/kratos/scaffolds/gen.go +++ b/examples/server/kratos/scaffolds/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,58 +249,63 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(c.Response(), c.Request(), "ListTodos", a.serveListTodos) + return nil +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListTodos", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListTodos", res) - return nil } // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(c.Response(), c.Request(), "CreateTodo", a.serveCreateTodo) + return nil +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreateTodo", err) - return nil + return } case "": a.failDecode(w, r, "CreateTodo", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreateTodo", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } res, err := a.svc.CreateTodo(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreateTodo", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreateTodo", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/kratos/split/api/gen.go b/examples/server/kratos/split/api/gen.go index d4e03dfc..fc2d0814 100644 --- a/examples/server/kratos/split/api/gen.go +++ b/examples/server/kratos/split/api/gen.go @@ -87,12 +87,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -119,6 +120,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -149,8 +157,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -162,31 +171,36 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(c.Response(), c.Request(), "GetBook", func(w http.ResponseWriter, r *http.Request) { + a.serveGetBook(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveGetBook(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(c.Vars().Get("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "GetBook", ParamName: "isbn", ParamLocation: "path", Err: err}) - return nil + return } res, err := a.svc.GetBook(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "GetBook", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "GetBook", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/examples/server/stdhttp/basic/gen.go b/examples/server/stdhttp/basic/gen.go index 40feaf01..6cb3f119 100644 --- a/examples/server/stdhttp/basic/gen.go +++ b/examples/server/stdhttp/basic/gen.go @@ -370,12 +370,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -402,6 +403,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -432,8 +440,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -445,12 +454,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -473,7 +486,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -503,7 +519,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -525,7 +544,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -547,7 +569,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/server/stdhttp/basic/gen_test.go b/examples/server/stdhttp/basic/gen_test.go index f6a5fb86..4b465d80 100644 --- a/examples/server/stdhttp/basic/gen_test.go +++ b/examples/server/stdhttp/basic/gen_test.go @@ -68,6 +68,14 @@ func TestRouter(t *testing.T) { servertest.Run(t, NewRouter(&service{pets: map[int]Pet{}}), servertest.Basic("404 page not found\n")) } +func TestOperationMiddleware(t *testing.T) { + t.Parallel() + + servertest.OperationMiddleware(t, func(mw func(http.Handler) http.Handler) http.Handler { + return NewRouter(&service{pets: map[int]Pet{}}, WithOperationMiddleware(mw)) + }) +} + func TestMethodNotAllowed(t *testing.T) { t.Parallel() diff --git a/examples/server/stdhttp/bodies/gen.go b/examples/server/stdhttp/bodies/gen.go index 3ccc80a3..d07ef56f 100644 --- a/examples/server/stdhttp/bodies/gen.go +++ b/examples/server/stdhttp/bodies/gen.go @@ -670,12 +670,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -702,6 +703,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -732,8 +740,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -745,12 +754,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostJSON handles POST /json. func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostJSON")) + a.operations.Serve(w, r, "PostJSON", a.servePostJSON) +} + +func (a *HTTPAdapter) servePostJSON(w http.ResponseWriter, r *http.Request) { opts := &PostJSONServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -778,7 +791,10 @@ func (a *HTTPAdapter) PostJSON(w http.ResponseWriter, r *http.Request) { // GetForm handles GET /form. func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetForm")) + a.operations.Serve(w, r, "GetForm", a.serveGetForm) +} + +func (a *HTTPAdapter) serveGetForm(w http.ResponseWriter, r *http.Request) { opts := &GetFormServiceRequestOptions{RawRequest: r} res, err := a.svc.GetForm(r.Context(), opts) @@ -795,7 +811,10 @@ func (a *HTTPAdapter) GetForm(w http.ResponseWriter, r *http.Request) { // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -825,7 +844,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // GetQuote handles GET /quote. func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetQuote")) + a.operations.Serve(w, r, "GetQuote", a.serveGetQuote) +} + +func (a *HTTPAdapter) serveGetQuote(w http.ResponseWriter, r *http.Request) { opts := &GetQuoteServiceRequestOptions{RawRequest: r} res, err := a.svc.GetQuote(r.Context(), opts) @@ -842,7 +864,10 @@ func (a *HTTPAdapter) GetQuote(w http.ResponseWriter, r *http.Request) { // GetCount handles GET /count. func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetCount")) + a.operations.Serve(w, r, "GetCount", a.serveGetCount) +} + +func (a *HTTPAdapter) serveGetCount(w http.ResponseWriter, r *http.Request) { opts := &GetCountServiceRequestOptions{RawRequest: r} res, err := a.svc.GetCount(r.Context(), opts) @@ -859,7 +884,10 @@ func (a *HTTPAdapter) GetCount(w http.ResponseWriter, r *http.Request) { // ListNotes handles GET /notes. func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListNotes")) + a.operations.Serve(w, r, "ListNotes", a.serveListNotes) +} + +func (a *HTTPAdapter) serveListNotes(w http.ResponseWriter, r *http.Request) { opts := &ListNotesServiceRequestOptions{RawRequest: r} res, err := a.svc.ListNotes(r.Context(), opts) @@ -876,7 +904,10 @@ func (a *HTTPAdapter) ListNotes(w http.ResponseWriter, r *http.Request) { // Upload handles POST /upload. func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Upload")) + a.operations.Serve(w, r, "Upload", a.serveUpload) +} + +func (a *HTTPAdapter) serveUpload(w http.ResponseWriter, r *http.Request) { opts := &UploadServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -907,7 +938,10 @@ func (a *HTTPAdapter) Upload(w http.ResponseWriter, r *http.Request) { // PostText handles POST /text. func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostText")) + a.operations.Serve(w, r, "PostText", a.servePostText) +} + +func (a *HTTPAdapter) servePostText(w http.ResponseWriter, r *http.Request) { opts := &PostTextServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "text/plain": @@ -944,7 +978,10 @@ func (a *HTTPAdapter) PostText(w http.ResponseWriter, r *http.Request) { // PutFile handles PUT /file. func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutFile")) + a.operations.Serve(w, r, "PutFile", a.servePutFile) +} + +func (a *HTTPAdapter) servePutFile(w http.ResponseWriter, r *http.Request) { opts := &PutFileServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "image/png": @@ -976,7 +1013,10 @@ func (a *HTTPAdapter) PutFile(w http.ResponseWriter, r *http.Request) { // PostAny handles POST /any. func (a *HTTPAdapter) PostAny(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAny")) + a.operations.Serve(w, r, "PostAny", a.servePostAny) +} + +func (a *HTTPAdapter) servePostAny(w http.ResponseWriter, r *http.Request) { opts := &PostAnyServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/xml": diff --git a/examples/server/stdhttp/errors/gen.go b/examples/server/stdhttp/errors/gen.go index 27f4b1e9..d5d2640a 100644 --- a/examples/server/stdhttp/errors/gen.go +++ b/examples/server/stdhttp/errors/gen.go @@ -298,13 +298,14 @@ var bodyPresence = runtime.Presence{ // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 - Presence runtime.PresenceChecker + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 + Presence runtime.PresenceChecker } // ServerOption sets one field of ServerOptions. @@ -332,6 +333,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -369,8 +377,9 @@ func WithPresence(p runtime.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -382,12 +391,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetPet handles GET /pets/{id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetPetPathParams{} @@ -424,7 +437,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // PutPet handles PUT /pets/{id}. func (a *HTTPAdapter) PutPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutPet")) + a.operations.Serve(w, r, "PutPet", a.servePutPet) +} + +func (a *HTTPAdapter) servePutPet(w http.ResponseWriter, r *http.Request) { opts := &PutPetServiceRequestOptions{RawRequest: r} opts.PathParams = &PutPetPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { diff --git a/examples/server/stdhttp/params/gen.go b/examples/server/stdhttp/params/gen.go index d28f31a9..e448240b 100644 --- a/examples/server/stdhttp/params/gen.go +++ b/examples/server/stdhttp/params/gen.go @@ -603,12 +603,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -635,6 +636,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -665,8 +673,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -678,12 +687,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PathStyles handles GET /path/{simple}/{label}/{matrix}/{list}. func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PathStyles")) + a.operations.Serve(w, r, "PathStyles", a.servePathStyles) +} + +func (a *HTTPAdapter) servePathStyles(w http.ResponseWriter, r *http.Request) { opts := &PathStylesServiceRequestOptions{RawRequest: r} opts.PathParams = &PathStylesPathParams{} if err := runtime.DecodePath(r.PathValue("simple"), runtime.Param{Name: "simple", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Simple); err != nil { @@ -717,7 +730,10 @@ func (a *HTTPAdapter) PathStyles(w http.ResponseWriter, r *http.Request) { // QueryStyles handles GET /query. func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "QueryStyles")) + a.operations.Serve(w, r, "QueryStyles", a.serveQueryStyles) +} + +func (a *HTTPAdapter) serveQueryStyles(w http.ResponseWriter, r *http.Request) { opts := &QueryStylesServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &QueryStylesQuery{} @@ -780,7 +796,10 @@ func (a *HTTPAdapter) QueryStyles(w http.ResponseWriter, r *http.Request) { // HeaderStyles handles GET /header. func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "HeaderStyles")) + a.operations.Serve(w, r, "HeaderStyles", a.serveHeaderStyles) +} + +func (a *HTTPAdapter) serveHeaderStyles(w http.ResponseWriter, r *http.Request) { opts := &HeaderStylesServiceRequestOptions{RawRequest: r} opts.Headers = &HeaderStylesHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Tags", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: false}, &opts.Headers.XTags); err != nil { @@ -814,7 +833,10 @@ func (a *HTTPAdapter) HeaderStyles(w http.ResponseWriter, r *http.Request) { // CookieStyles handles GET /cookie. func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CookieStyles")) + a.operations.Serve(w, r, "CookieStyles", a.serveCookieStyles) +} + +func (a *HTTPAdapter) serveCookieStyles(w http.ResponseWriter, r *http.Request) { opts := &CookieStylesServiceRequestOptions{RawRequest: r} opts.Cookies = &CookieStylesCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false}, &opts.Cookies.Session); err != nil { @@ -840,7 +862,10 @@ func (a *HTTPAdapter) CookieStyles(w http.ResponseWriter, r *http.Request) { // Search handles GET /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Search", ParamName: "filter", ParamLocation: "querystring", Err: err}) diff --git a/examples/server/stdhttp/paths/gen.go b/examples/server/stdhttp/paths/gen.go index 541f5e25..bcd05a3d 100644 --- a/examples/server/stdhttp/paths/gen.go +++ b/examples/server/stdhttp/paths/gen.go @@ -806,12 +806,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -838,6 +839,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -868,8 +876,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -881,12 +890,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetName handles GET /v1/{name}. func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetName")) + a.operations.Serve(w, r, "GetName", a.serveGetName) +} + +func (a *HTTPAdapter) serveGetName(w http.ResponseWriter, r *http.Request) { opts := &GetNameServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.PathParams = &GetNamePathParams{} @@ -914,7 +927,10 @@ func (a *HTTPAdapter) GetName(w http.ResponseWriter, r *http.Request) { // DeleteName handles DELETE /v1/{name}. func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeleteName")) + a.operations.Serve(w, r, "DeleteName", a.serveDeleteName) +} + +func (a *HTTPAdapter) serveDeleteName(w http.ResponseWriter, r *http.Request) { opts := &DeleteNameServiceRequestOptions{RawRequest: r} opts.PathParams = &DeleteNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -947,7 +963,10 @@ func (a *HTTPAdapter) DeleteName(w http.ResponseWriter, r *http.Request) { // TraceName handles TRACE /v1/{name}. func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "TraceName")) + a.operations.Serve(w, r, "TraceName", a.serveTraceName) +} + +func (a *HTTPAdapter) serveTraceName(w http.ResponseWriter, r *http.Request) { opts := &TraceNameServiceRequestOptions{RawRequest: r} opts.PathParams = &TraceNamePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -969,7 +988,10 @@ func (a *HTTPAdapter) TraceName(w http.ResponseWriter, r *http.Request) { // GetNameSlash handles GET /v1/{name}/. func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetNameSlash")) + a.operations.Serve(w, r, "GetNameSlash", a.serveGetNameSlash) +} + +func (a *HTTPAdapter) serveGetNameSlash(w http.ResponseWriter, r *http.Request) { opts := &GetNameSlashServiceRequestOptions{RawRequest: r} opts.PathParams = &GetNameSlashPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -991,7 +1013,10 @@ func (a *HTTPAdapter) GetNameSlash(w http.ResponseWriter, r *http.Request) { // GetPolicy handles GET /v1/{name}:getIamPolicy. func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPolicy")) + a.operations.Serve(w, r, "GetPolicy", a.serveGetPolicy) +} + +func (a *HTTPAdapter) serveGetPolicy(w http.ResponseWriter, r *http.Request) { opts := &GetPolicyServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPolicyPathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1013,7 +1038,10 @@ func (a *HTTPAdapter) GetPolicy(w http.ResponseWriter, r *http.Request) { // GetChildren handles GET /v1/{id}/children. func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetChildren")) + a.operations.Serve(w, r, "GetChildren", a.serveGetChildren) +} + +func (a *HTTPAdapter) serveGetChildren(w http.ResponseWriter, r *http.Request) { opts := &GetChildrenServiceRequestOptions{RawRequest: r} opts.PathParams = &GetChildrenPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1035,7 +1063,10 @@ func (a *HTTPAdapter) GetChildren(w http.ResponseWriter, r *http.Request) { // PutBatch handles PUT /v1/:batch. func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PutBatch")) + a.operations.Serve(w, r, "PutBatch", a.servePutBatch) +} + +func (a *HTTPAdapter) servePutBatch(w http.ResponseWriter, r *http.Request) { opts := &PutBatchServiceRequestOptions{RawRequest: r} res, err := a.svc.PutBatch(r.Context(), opts) @@ -1052,7 +1083,10 @@ func (a *HTTPAdapter) PutBatch(w http.ResponseWriter, r *http.Request) { // GetGeo handles GET /geo/{lat:lng}. func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetGeo")) + a.operations.Serve(w, r, "GetGeo", a.serveGetGeo) +} + +func (a *HTTPAdapter) serveGetGeo(w http.ResponseWriter, r *http.Request) { opts := &GetGeoServiceRequestOptions{RawRequest: r} opts.PathParams = &GetGeoPathParams{} if err := runtime.DecodePath(r.PathValue("lat_lng"), runtime.Param{Name: "lat:lng", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.LatLng); err != nil { @@ -1074,7 +1108,10 @@ func (a *HTTPAdapter) GetGeo(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/{name}.{ext}. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} opts.PathParams = &GetFilePathParams{} if err := runtime.DecodePath(r.PathValue("name"), runtime.Param{Name: "name", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Name); err != nil { @@ -1100,7 +1137,10 @@ func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // GetProduct handles GET /products({id}). func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetProduct")) + a.operations.Serve(w, r, "GetProduct", a.serveGetProduct) +} + +func (a *HTTPAdapter) serveGetProduct(w http.ResponseWriter, r *http.Request) { opts := &GetProductServiceRequestOptions{RawRequest: r} opts.PathParams = &GetProductPathParams{} if err := runtime.DecodePath(r.PathValue("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -1122,7 +1162,10 @@ func (a *HTTPAdapter) GetProduct(w http.ResponseWriter, r *http.Request) { // GetMetadata handles GET /$metadata. func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetMetadata")) + a.operations.Serve(w, r, "GetMetadata", a.serveGetMetadata) +} + +func (a *HTTPAdapter) serveGetMetadata(w http.ResponseWriter, r *http.Request) { opts := &GetMetadataServiceRequestOptions{RawRequest: r} res, err := a.svc.GetMetadata(r.Context(), opts) @@ -1139,7 +1182,10 @@ func (a *HTTPAdapter) GetMetadata(w http.ResponseWriter, r *http.Request) { // GetColon handles GET /a:b. func (a *HTTPAdapter) GetColon(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetColon")) + a.operations.Serve(w, r, "GetColon", a.serveGetColon) +} + +func (a *HTTPAdapter) serveGetColon(w http.ResponseWriter, r *http.Request) { opts := &GetColonServiceRequestOptions{RawRequest: r} res, err := a.svc.GetColon(r.Context(), opts) diff --git a/examples/server/stdhttp/routes/gen.go b/examples/server/stdhttp/routes/gen.go index 2e4110ed..5e166521 100644 --- a/examples/server/stdhttp/routes/gen.go +++ b/examples/server/stdhttp/routes/gen.go @@ -353,12 +353,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -385,6 +386,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -415,8 +423,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -428,12 +437,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // Search handles QUERY /search. func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Search")) + a.operations.Serve(w, r, "Search", a.serveSearch) +} + +func (a *HTTPAdapter) serveSearch(w http.ResponseWriter, r *http.Request) { opts := &SearchServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -463,7 +476,10 @@ func (a *HTTPAdapter) Search(w http.ResponseWriter, r *http.Request) { // PurgeSearch handles PURGE /search. func (a *HTTPAdapter) PurgeSearch(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PurgeSearch")) + a.operations.Serve(w, r, "PurgeSearch", a.servePurgeSearch) +} + +func (a *HTTPAdapter) servePurgeSearch(w http.ResponseWriter, r *http.Request) { opts := &PurgeSearchServiceRequestOptions{RawRequest: r} res, err := a.svc.PurgeSearch(r.Context(), opts) @@ -480,7 +496,10 @@ func (a *HTTPAdapter) PurgeSearch(w http.ResponseWriter, r *http.Request) { // ListPets handles GET /pets/. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} res, err := a.svc.ListPets(r.Context(), opts) @@ -497,7 +516,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // GetPet handles GET /pets/{pet-id}. func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetPet")) + a.operations.Serve(w, r, "GetPet", a.serveGetPet) +} + +func (a *HTTPAdapter) serveGetPet(w http.ResponseWriter, r *http.Request) { opts := &GetPetServiceRequestOptions{RawRequest: r} opts.PathParams = &GetPetPathParams{} if err := runtime.DecodePath(r.PathValue("pet_id"), runtime.Param{Name: "pet-id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.PetID); err != nil { @@ -519,7 +541,10 @@ func (a *HTTPAdapter) GetPet(w http.ResponseWriter, r *http.Request) { // GetFile handles GET /files/*. func (a *HTTPAdapter) GetFile(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetFile")) + a.operations.Serve(w, r, "GetFile", a.serveGetFile) +} + +func (a *HTTPAdapter) serveGetFile(w http.ResponseWriter, r *http.Request) { opts := &GetFileServiceRequestOptions{RawRequest: r} res, err := a.svc.GetFile(r.Context(), opts) diff --git a/examples/server/stdhttp/scaffolds/gen.go b/examples/server/stdhttp/scaffolds/gen.go index b502fd95..053ec0ba 100644 --- a/examples/server/stdhttp/scaffolds/gen.go +++ b/examples/server/stdhttp/scaffolds/gen.go @@ -164,12 +164,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -196,6 +197,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -226,8 +234,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc TodoInterface - opts *ServerOptions + svc TodoInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -239,12 +248,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc TodoInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListTodos handles GET /todos. func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListTodos")) + a.operations.Serve(w, r, "ListTodos", a.serveListTodos) +} + +func (a *HTTPAdapter) serveListTodos(w http.ResponseWriter, r *http.Request) { opts := &ListTodosServiceRequestOptions{RawRequest: r} res, err := a.svc.ListTodos(r.Context(), opts) @@ -261,7 +274,10 @@ func (a *HTTPAdapter) ListTodos(w http.ResponseWriter, r *http.Request) { // CreateTodo handles POST /todos. func (a *HTTPAdapter) CreateTodo(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreateTodo")) + a.operations.Serve(w, r, "CreateTodo", a.serveCreateTodo) +} + +func (a *HTTPAdapter) serveCreateTodo(w http.ResponseWriter, r *http.Request) { opts := &CreateTodoServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/server/stdhttp/split/api/gen.go b/examples/server/stdhttp/split/api/gen.go index cac0818c..5fa6147f 100644 --- a/examples/server/stdhttp/split/api/gen.go +++ b/examples/server/stdhttp/split/api/gen.go @@ -86,12 +86,13 @@ func (r *GetBookResponseData) ContentType() string { // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -118,6 +119,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -148,8 +156,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc BooksInterface - opts *ServerOptions + svc BooksInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -161,12 +170,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc BooksInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // GetBook handles GET /books/{isbn}. func (a *HTTPAdapter) GetBook(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "GetBook")) + a.operations.Serve(w, r, "GetBook", a.serveGetBook) +} + +func (a *HTTPAdapter) serveGetBook(w http.ResponseWriter, r *http.Request) { opts := &GetBookServiceRequestOptions{RawRequest: r} opts.PathParams = &models.GetBookPathParams{} if err := runtime.DecodePath(r.PathValue("isbn"), runtime.Param{Name: "isbn", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.Isbn); err != nil { diff --git a/examples/templates/blocks/gen.go b/examples/templates/blocks/gen.go index 32de2825..bfd22cf2 100644 --- a/examples/templates/blocks/gen.go +++ b/examples/templates/blocks/gen.go @@ -165,12 +165,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -197,6 +198,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -227,8 +235,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -240,12 +249,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} res, err := a.svc.ListPets(r.Context(), opts) @@ -262,7 +275,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": diff --git a/examples/templates/wrapper/gen.go b/examples/templates/wrapper/gen.go index 3fe6bb74..1cc3c04c 100644 --- a/examples/templates/wrapper/gen.go +++ b/examples/templates/wrapper/gen.go @@ -296,12 +296,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -328,6 +329,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -358,8 +366,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -371,12 +380,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} res, err := a.svc.ListPets(r.Context(), opts) @@ -393,7 +406,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -423,7 +439,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -445,7 +464,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} res, err := a.svc.Ping(r.Context(), opts) diff --git a/examples/unions/server/gen.go b/examples/unions/server/gen.go index d964b204..a63e35c7 100644 --- a/examples/unions/server/gen.go +++ b/examples/unions/server/gen.go @@ -495,12 +495,13 @@ const ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -527,6 +528,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -557,8 +565,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc ServiceInterface - opts *ServerOptions + svc ServiceInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -570,12 +579,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc ServiceInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // PostForm handles POST /form. func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostForm")) + a.operations.Serve(w, r, "PostForm", a.servePostForm) +} + +func (a *HTTPAdapter) servePostForm(w http.ResponseWriter, r *http.Request) { opts := &PostFormServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": @@ -610,7 +623,10 @@ func (a *HTTPAdapter) PostForm(w http.ResponseWriter, r *http.Request) { // PostMultipart handles POST /multipart. func (a *HTTPAdapter) PostMultipart(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostMultipart")) + a.operations.Serve(w, r, "PostMultipart", a.servePostMultipart) +} + +func (a *HTTPAdapter) servePostMultipart(w http.ResponseWriter, r *http.Request) { opts := &PostMultipartServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "multipart/form-data": @@ -641,7 +657,10 @@ func (a *HTTPAdapter) PostMultipart(w http.ResponseWriter, r *http.Request) { // PostAttachment handles POST /attachments. func (a *HTTPAdapter) PostAttachment(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "PostAttachment")) + a.operations.Serve(w, r, "PostAttachment", a.servePostAttachment) +} + +func (a *HTTPAdapter) servePostAttachment(w http.ResponseWriter, r *http.Request) { opts := &PostAttachmentServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/x-www-form-urlencoded": diff --git a/internal/gen/server/adapter.go b/internal/gen/server/adapter.go index f2fbb3ca..94516088 100644 --- a/internal/gen/server/adapter.go +++ b/internal/gen/server/adapter.go @@ -76,7 +76,9 @@ type AdapterView struct { Rejects []RejectView } -// HandlerView is one handler method. ID is the operation name as a string literal. Bodies are +// HandlerView is one handler method. ID is the operation name as a string literal. Serve is the +// method that serves the operation inside the operation middleware, with its signature; Context +// is the framework's context it takes first, when it reads path parameters from it. Bodies are // the media types the handler tells apart, each once and never an empty one; Patterns are the // keys that take the media types they match, application/*+json any JSON and a range such as // text/* its type; Wildcards holds the body that takes any other media type, when the operation @@ -85,6 +87,9 @@ type AdapterView struct { type HandlerView struct { Name string ID string + Serve string + ServeSignature string + Context string Method string Path string Options string @@ -125,10 +130,10 @@ type ParamView struct { } // BodyView is one media type of the request body. MediaType is quoted, in lower case and without -// parameters; Case is the case of the body switch that takes it; OperationID is quoted; Target is the address of the options field; Type is the -// struct a multipart form fills; Assign is the expression that turns text, data or file, the -// decoded body, into the field's type; Return is the statement that leaves the handler. Check is -// what the body is checked against before it is decoded, nil when it is not. Encoding is nil or the +// parameters; Case is the case of the body switch that takes it; OperationID is quoted; Target is +// the address of the options field; Type is the struct a multipart form fills; Assign is the +// expression that turns text, data or file, the decoded body, into the field's type. Check is what +// the body is checked against before it is decoded, nil when it is not. Encoding is nil or the // variable encoding, which EncodingLiteral sets. type BodyView struct { Kind string @@ -148,18 +153,16 @@ type BodyView struct { Target string Type string Assign string - Return string Check *PropView Encoding string EncodingLiteral string } -// bodyAt is what the bodies of one operation share: the operation, the statement that leaves its -// handler, and the table its bodies are checked against. +// bodyAt is what the bodies of one operation share: the operation and the table its bodies are +// checked against. type bodyAt struct { id string isRequired bool - ret string scope *gocode.Scope table *presenceTable } @@ -212,16 +215,22 @@ func adapterView(g *Generator, s *gocode.Scope) *AdapterView { } func handlerView(g *Generator, op *gomodel.Operation, s *gocode.Scope, table *presenceTable) HandlerView { + h := g.opts.Framework.Handler(s) v := HandlerView{ - Name: op.Name, - ID: gocode.Quote(op.Name), - Method: op.Spec.Method, - Path: op.Spec.Path, - Options: s.Symbol(PartService, g.opts.Namer.ServiceRequestOptions(op.Name)), + Name: op.Name, + ID: gocode.Quote(op.Name), + Serve: "serve" + op.Name, + ServeSignature: h.ServeSignature, + Method: op.Spec.Method, + Path: op.Spec.Path, + Options: s.Symbol(PartService, g.opts.Namer.ServiceRequestOptions(op.Name)), } for _, p := range op.Params { v.HasQuery = v.HasQuery || p.In == spec.InQuery v.Groups = append(v.Groups, groupView(g, p, s)) + if p.In == spec.InPath && h.Context != "" { + v.Context, v.ServeSignature = h.Context, h.ContextServeSignature + } } if qs := op.QueryString; qs != nil { v.QueryString = &ParamView{ @@ -236,7 +245,7 @@ func handlerView(g *Generator, op *gomodel.Operation, s *gocode.Scope, table *pr } v.IsBodyRequired = op.Spec.Body != nil && op.Spec.Body.Required - at := bodyAt{id: v.ID, isRequired: v.IsBodyRequired, ret: g.opts.Framework.Handler(s).Return, scope: s, table: table} + at := bodyAt{id: v.ID, isRequired: v.IsBodyRequired, scope: s, table: table} fields := operation.BodyFields(op.Bodies, g.opts.Namer) var jsonPatterns, ranges []BodyView seen := []string{""} @@ -331,7 +340,6 @@ func bodyView(c gomodel.Content, field string, at bodyAt) BodyView { IsRequired: at.isRequired, Field: field, Target: gocode.AddressOf(gocode.Selector("opts", field)), - Return: at.ret, } v.Case = bodyCase(operation.BaseMediaType(c.MediaType), v.Runtime) t := operation.BodyType(c) diff --git a/internal/gen/server/adapter_test.go b/internal/gen/server/adapter_test.go index 96d89207..90605c80 100644 --- a/internal/gen/server/adapter_test.go +++ b/internal/gen/server/adapter_test.go @@ -44,7 +44,7 @@ func TestBodyView(t *testing.T) { m := &gomodel.Model{Decls: []*gomodel.Decl{pet, blob, image}} g, _ := New(m, allOptions()) view := func(v BodyView) BodyView { - v.Runtime, v.OperationID, v.IsRequired, v.Field, v.Target, v.Return = "runtime", `"Op"`, true, "Body", "&opts.Body", "return" + v.Runtime, v.OperationID, v.IsRequired, v.Field, v.Target = "runtime", `"Op"`, true, "Body", "&opts.Body" if v.Case == "" { v.Case = v.MediaType } @@ -170,7 +170,7 @@ func TestBodyView(t *testing.T) { t.Parallel() // bodyView imports into the scope, so each subtest has its own. - at := bodyAt{id: `"Op"`, isRequired: true, ret: "return", scope: fixture{m: m, g: g, cfg: scaffoldConfig}.scope(t, PartAdapter), table: newPresenceTable(nil, true, "runtime")} + at := bodyAt{id: `"Op"`, isRequired: true, scope: fixture{m: m, g: g, cfg: scaffoldConfig}.scope(t, PartAdapter), table: newPresenceTable(nil, true, "runtime")} assert.Equal(t, tc.want, bodyView(tc.content, "Body", at)) }) diff --git a/internal/gen/server/framework/beego/beego_test.go b/internal/gen/server/framework/beego/beego_test.go index d2343d45..694743b3 100644 --- a/internal/gen/server/framework/beego/beego_test.go +++ b/internal/gen/server/framework/beego/beego_test.go @@ -97,7 +97,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/chi/chi_test.go b/internal/gen/server/framework/chi/chi_test.go index 98a7022e..032a7785 100644 --- a/internal/gen/server/framework/chi/chi_test.go +++ b/internal/gen/server/framework/chi/chi_test.go @@ -95,7 +95,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/echo/echo.go b/internal/gen/server/framework/echo/echo.go index f6f11471..7c824821 100644 --- a/internal/gen/server/framework/echo/echo.go +++ b/internal/gen/server/framework/echo/echo.go @@ -53,7 +53,7 @@ func (Framework) Conflicts(routes []framework.Route) ([]framework.Route, []frame // Handler is echo's own shape, a handler of its context. func (Framework) Handler(s *gocode.Scope) framework.Handler { - return framework.ContextHandler(gocode.Selector(s.Import(gomodel.Import{Path: importPath}), "Context")) + return framework.ContextHandler(s, gocode.Selector(s.Import(gomodel.Import{Path: importPath}), "Context")) } // PathParam unescapes the value, which echo cuts from the raw path when the request has one. diff --git a/internal/gen/server/framework/echo/echo_test.go b/internal/gen/server/framework/echo/echo_test.go index 978f5f2d..c2cef8e1 100644 --- a/internal/gen/server/framework/echo/echo_test.go +++ b/internal/gen/server/framework/echo/echo_test.go @@ -100,12 +100,15 @@ func TestHandler(t *testing.T) { s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) assert.Equal(t, framework.Handler{ - Signature: "(c echo.Context) error", - Prologue: "w, r := c.Response(), c.Request()", - Return: "return nil", - Epilogue: "return nil", + Signature: "(c echo.Context) error", + Writer: "c.Response()", + Request: "c.Request()", + Epilogue: "return nil", + ServeSignature: "(w http.ResponseWriter, r *http.Request)", + Context: "c", + ContextServeSignature: "(c echo.Context, w http.ResponseWriter, r *http.Request)", }, Framework{}.Handler(s)) - assert.Equal(t, "import echo \"github.com/labstack/echo/v4\"", s.Imports.Decl()) + assert.Equal(t, "import (\n\t\"net/http\"\n\n\techo \"github.com/labstack/echo/v4\"\n)", s.Imports.Decl()) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/echov5/echov5.go b/internal/gen/server/framework/echov5/echov5.go index 8c3b84fd..061392d9 100644 --- a/internal/gen/server/framework/echov5/echov5.go +++ b/internal/gen/server/framework/echov5/echov5.go @@ -53,7 +53,7 @@ func (Framework) Conflicts(routes []framework.Route) ([]framework.Route, []frame // Handler is echo's own shape, a handler of its context. func (Framework) Handler(s *gocode.Scope) framework.Handler { - return framework.ContextHandler(gocode.Deref(gocode.Selector(s.Import(gomodel.Import{Path: importPath}), "Context"))) + return framework.ContextHandler(s, gocode.Deref(gocode.Selector(s.Import(gomodel.Import{Path: importPath}), "Context"))) } // PathParam unescapes the value, which echo cuts from the raw path when the request has one. diff --git a/internal/gen/server/framework/echov5/echov5_test.go b/internal/gen/server/framework/echov5/echov5_test.go index 251314f1..1bb96977 100644 --- a/internal/gen/server/framework/echov5/echov5_test.go +++ b/internal/gen/server/framework/echov5/echov5_test.go @@ -92,12 +92,15 @@ func TestHandler(t *testing.T) { s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) assert.Equal(t, framework.Handler{ - Signature: "(c *echo.Context) error", - Prologue: "w, r := c.Response(), c.Request()", - Return: "return nil", - Epilogue: "return nil", + Signature: "(c *echo.Context) error", + Writer: "c.Response()", + Request: "c.Request()", + Epilogue: "return nil", + ServeSignature: "(w http.ResponseWriter, r *http.Request)", + Context: "c", + ContextServeSignature: "(c *echo.Context, w http.ResponseWriter, r *http.Request)", }, Framework{}.Handler(s)) - assert.Equal(t, "import echo \"github.com/labstack/echo/v5\"", s.Imports.Decl()) + assert.Equal(t, "import (\n\t\"net/http\"\n\n\techo \"github.com/labstack/echo/v5\"\n)", s.Imports.Decl()) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/fasthttp/fasthttp_test.go b/internal/gen/server/framework/fasthttp/fasthttp_test.go index fa3cc818..5826c7a2 100644 --- a/internal/gen/server/framework/fasthttp/fasthttp_test.go +++ b/internal/gen/server/framework/fasthttp/fasthttp_test.go @@ -153,7 +153,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/fiber/fiber_test.go b/internal/gen/server/framework/fiber/fiber_test.go index 108956ff..e39c62fd 100644 --- a/internal/gen/server/framework/fiber/fiber_test.go +++ b/internal/gen/server/framework/fiber/fiber_test.go @@ -105,7 +105,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/framework.go b/internal/gen/server/framework/framework.go index 70d17b92..8a7e4248 100644 --- a/internal/gen/server/framework/framework.go +++ b/internal/gen/server/framework/framework.go @@ -46,15 +46,20 @@ type Conflict struct { } // Handler is the shape of the handlers the adapter gives a framework. Signature follows the -// handler's name: its parameters, and its result when it has one. Prologue is the statement that -// binds w and r, the response writer and the request, when the parameters are not those; Return -// is the statement that leaves the handler early and Epilogue the one that ends it, both empty -// when the handler needs none. +// handler's name: its parameters, and its result when it has one. Writer and Request are the +// response writer and the request the handler has; Epilogue is the statement that ends it, empty +// when it needs none. ServeSignature is the shape of the method that serves an operation inside +// the operation middleware, which takes w and r. Context is the framework's context, when the +// handler has one; ContextServeSignature then takes it before w and r, for an operation that +// reads its path parameters from it. type Handler struct { - Signature string - Prologue string - Return string - Epilogue string + Signature string + Writer string + Request string + Epilogue string + ServeSignature string + Context string + ContextServeSignature string } // Framework is one HTTP framework a router is generated for. @@ -143,18 +148,21 @@ func (c Colon) Pattern(path string) (string, error) { // HTTPHandler is the handler shape of an http.HandlerFunc. func HTTPHandler(s *gocode.Scope) Handler { - pkg := s.Import(gomodel.Import{Path: "net/http"}) - params := []string{gocode.Param("w", gocode.Selector(pkg, "ResponseWriter")), gocode.Param("r", gocode.Deref(gocode.Selector(pkg, "Request")))} - return Handler{Signature: gocode.Signature(params, ""), Return: gocode.Return()} + signature := gocode.Signature(httpParams(s), "") + return Handler{Signature: signature, Writer: "w", Request: "r", ServeSignature: signature} } // ContextHandler is the shape of a handler that takes the router's context and returns nil. -func ContextHandler(ctxType string) Handler { +func ContextHandler(s *gocode.Scope, ctxType string) Handler { + c := gocode.Param("c", ctxType) return Handler{ - Signature: gocode.Signature([]string{gocode.Param("c", ctxType)}, "error"), - Prologue: gocode.Define([]string{"w", "r"}, gocode.Call(gocode.Selector("c", "Response")), gocode.Call(gocode.Selector("c", "Request"))), - Return: gocode.Return("nil"), - Epilogue: gocode.Return("nil"), + Signature: gocode.Signature([]string{c}, "error"), + Writer: gocode.Call(gocode.Selector("c", "Response")), + Request: gocode.Call(gocode.Selector("c", "Request")), + Epilogue: gocode.Return("nil"), + ServeSignature: gocode.Signature(httpParams(s), ""), + Context: "c", + ContextServeSignature: gocode.Signature(append([]string{c}, httpParams(s)...), ""), } } @@ -377,3 +385,9 @@ func ranks(path string) []int { } return out } + +// httpParams are the parameters of an http.HandlerFunc, w and r. +func httpParams(s *gocode.Scope) []string { + pkg := s.Import(gomodel.Import{Path: "net/http"}) + return []string{gocode.Param("w", gocode.Selector(pkg, "ResponseWriter")), gocode.Param("r", gocode.Deref(gocode.Selector(pkg, "Request")))} +} diff --git a/internal/gen/server/framework/framework_test.go b/internal/gen/server/framework/framework_test.go index 5f9bc7a4..4224b7c8 100644 --- a/internal/gen/server/framework/framework_test.go +++ b/internal/gen/server/framework/framework_test.go @@ -22,19 +22,26 @@ func TestHTTPHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, HTTPHandler(s)) + assert.Equal(t, Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, HTTPHandler(s)) assert.Equal(t, `import "net/http"`, s.Imports.Decl()) } func TestContextHandler(t *testing.T) { t.Parallel() + f := &layout.File{Path: "/work/gen.go", Package: "api"} + s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) + assert.Equal(t, Handler{ - Signature: "(c echo.Context) error", - Prologue: "w, r := c.Response(), c.Request()", - Return: "return nil", - Epilogue: "return nil", - }, ContextHandler("echo.Context")) + Signature: "(c echo.Context) error", + Writer: "c.Response()", + Request: "c.Request()", + Epilogue: "return nil", + ServeSignature: "(w http.ResponseWriter, r *http.Request)", + Context: "c", + ContextServeSignature: "(c echo.Context, w http.ResponseWriter, r *http.Request)", + }, ContextHandler(s, "echo.Context")) + assert.Equal(t, `import "net/http"`, s.Imports.Decl()) } func TestParams(t *testing.T) { diff --git a/internal/gen/server/framework/gin/gin_test.go b/internal/gen/server/framework/gin/gin_test.go index 9e109668..f41623a3 100644 --- a/internal/gen/server/framework/gin/gin_test.go +++ b/internal/gen/server/framework/gin/gin_test.go @@ -161,7 +161,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/goframe/goframe_test.go b/internal/gen/server/framework/goframe/goframe_test.go index 09e182c0..8d498bcd 100644 --- a/internal/gen/server/framework/goframe/goframe_test.go +++ b/internal/gen/server/framework/goframe/goframe_test.go @@ -110,7 +110,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/gorillamux/gorillamux_test.go b/internal/gen/server/framework/gorillamux/gorillamux_test.go index 89414554..f371d961 100644 --- a/internal/gen/server/framework/gorillamux/gorillamux_test.go +++ b/internal/gen/server/framework/gorillamux/gorillamux_test.go @@ -99,7 +99,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/gozero/gozero_test.go b/internal/gen/server/framework/gozero/gozero_test.go index 4d452104..2cd6d352 100644 --- a/internal/gen/server/framework/gozero/gozero_test.go +++ b/internal/gen/server/framework/gozero/gozero_test.go @@ -95,7 +95,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/hertz/hertz_test.go b/internal/gen/server/framework/hertz/hertz_test.go index 228b5dc1..898ae5dc 100644 --- a/internal/gen/server/framework/hertz/hertz_test.go +++ b/internal/gen/server/framework/hertz/hertz_test.go @@ -92,7 +92,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/iris/iris_test.go b/internal/gen/server/framework/iris/iris_test.go index 28acfc5c..ec172d4d 100644 --- a/internal/gen/server/framework/iris/iris_test.go +++ b/internal/gen/server/framework/iris/iris_test.go @@ -103,7 +103,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/kratos/kratos.go b/internal/gen/server/framework/kratos/kratos.go index f4aace68..d9a4932b 100644 --- a/internal/gen/server/framework/kratos/kratos.go +++ b/internal/gen/server/framework/kratos/kratos.go @@ -56,7 +56,7 @@ func (Framework) Conflicts(routes []framework.Route) ([]framework.Route, []frame // Handler is kratos's own shape, a handler of its context. func (Framework) Handler(s *gocode.Scope) framework.Handler { - return framework.ContextHandler(gocode.Selector(s.Import(gomodel.Import{Path: importPath, Alias: "khttp"}), "Context")) + return framework.ContextHandler(s, gocode.Selector(s.Import(gomodel.Import{Path: importPath, Alias: "khttp"}), "Context")) } func (Framework) PathParam(_ *gocode.Scope, name string) string { diff --git a/internal/gen/server/framework/kratos/kratos_test.go b/internal/gen/server/framework/kratos/kratos_test.go index 5b8d3757..ae41c04a 100644 --- a/internal/gen/server/framework/kratos/kratos_test.go +++ b/internal/gen/server/framework/kratos/kratos_test.go @@ -93,12 +93,15 @@ func TestHandler(t *testing.T) { s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) assert.Equal(t, framework.Handler{ - Signature: "(c khttp.Context) error", - Prologue: "w, r := c.Response(), c.Request()", - Return: "return nil", - Epilogue: "return nil", + Signature: "(c khttp.Context) error", + Writer: "c.Response()", + Request: "c.Request()", + Epilogue: "return nil", + ServeSignature: "(w http.ResponseWriter, r *http.Request)", + Context: "c", + ContextServeSignature: "(c khttp.Context, w http.ResponseWriter, r *http.Request)", }, Framework{}.Handler(s)) - assert.Equal(t, "import khttp \"github.com/go-kratos/kratos/v2/transport/http\"", s.Imports.Decl()) + assert.Equal(t, "import (\n\t\"net/http\"\n\n\tkhttp \"github.com/go-kratos/kratos/v2/transport/http\"\n)", s.Imports.Decl()) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/framework/stdhttp/stdhttp_test.go b/internal/gen/server/framework/stdhttp/stdhttp_test.go index b484cfb9..1f64620c 100644 --- a/internal/gen/server/framework/stdhttp/stdhttp_test.go +++ b/internal/gen/server/framework/stdhttp/stdhttp_test.go @@ -237,7 +237,7 @@ func TestHandler(t *testing.T) { f := &layout.File{Path: "/work/gen.go", Package: "api"} s := gocode.NewScope(f, &layout.Layout{Files: []*layout.File{f}}) - assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Return: "return"}, Framework{}.Handler(s)) + assert.Equal(t, framework.Handler{Signature: "(w http.ResponseWriter, r *http.Request)", Writer: "w", Request: "r", ServeSignature: "(w http.ResponseWriter, r *http.Request)"}, Framework{}.Handler(s)) } func TestPathParam(t *testing.T) { diff --git a/internal/gen/server/templates/adapter.tmpl b/internal/gen/server/templates/adapter.tmpl index a8fdd449..219acb13 100644 --- a/internal/gen/server/templates/adapter.tmpl +++ b/internal/gen/server/templates/adapter.tmpl @@ -20,14 +20,15 @@ var bodyPresence = {{.Runtime}}.Presence{ {{end -}} // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func({{.HTTP}}.Handler) {{.HTTP}}.Handler - ErrorHandler {{.HTTPServer}}.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func({{.HTTP}}.Handler) {{.HTTP}}.Handler + OperationMiddleware []func({{.HTTP}}.Handler) {{.HTTP}}.Handler + ErrorHandler {{.HTTPServer}}.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 {{- if .Presence}} - Presence {{.Runtime}}.PresenceChecker + Presence {{.Runtime}}.PresenceChecker {{- end}} } @@ -58,6 +59,13 @@ func WithMiddleware(mw ...func({{.HTTP}}.Handler) {{.HTTP}}.Handler) ServerOptio } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func({{.HTTP}}.Handler) {{.HTTP}}.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h {{.HTTPServer}}.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -97,8 +105,9 @@ func WithPresence(p {{.Runtime}}.PresenceChecker) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc {{.Service}} - opts *ServerOptions + svc {{.Service}} + opts *ServerOptions + operations {{.HTTPServer}}.Chain } type responseData interface { @@ -110,16 +119,26 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc {{.Service}}, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: {{.HTTPServer}}.NewChain(o.OperationMiddleware...)} } {{- range $op := .Operations}} // {{.Name}} handles {{.Method}} {{.Path}}. func (a *HTTPAdapter) {{.Name}}{{$.Handler.Signature}} { -{{- if $.Handler.Prologue}} - {{$.Handler.Prologue}} +{{- if .Context}} + a.operations.Serve({{$.Handler.Writer}}, {{$.Handler.Request}}, {{.ID}}, func(w {{$.HTTP}}.ResponseWriter, r *{{$.HTTP}}.Request) { + a.{{.Serve}}({{.Context}}, w, r) + }) +{{- else}} + a.operations.Serve({{$.Handler.Writer}}, {{$.Handler.Request}}, {{.ID}}, a.{{.Serve}}) {{- end}} - r = r.WithContext({{$.Runtime}}.WithOperationID(r.Context(), {{.ID}})) +{{- if $.Handler.Epilogue}} + {{$.Handler.Epilogue}} +{{- end}} +} + +func (a *HTTPAdapter) {{.Serve}}{{.ServeSignature}} { opts := &{{.Options}}{RawRequest: r} {{- if .HasQuery}} query := {{$.Runtime}}.ParseQuery(r.URL.RawQuery) @@ -129,14 +148,14 @@ func (a *HTTPAdapter) {{.Name}}{{$.Handler.Signature}} { {{- range .Params}} if err := {{$.Runtime}}.{{.Decoder}}({{.Source}}, {{$.Runtime}}.Param{Name: {{.Name}}, Style: {{$.Runtime}}.{{.Style}}, IsExplode: {{.IsExplode}}, IsRequired: {{.IsRequired}}, IsJSON: {{.IsJSON}}{{with .Default}}, Default: {{.}}{{end}}}, {{.Target}}); err != nil { a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorParse, OperationID: {{$op.ID}}, ParamName: {{.Name}}, ParamLocation: {{.Location}}, Err: err}) - {{$.Handler.Return}} + return } {{- end}} {{- end}} {{- with .QueryString}} if err := {{$.Runtime}}.DecodeQueryString({{.Source}}, {{$.Runtime}}.Param{Name: {{.Name}}, IsRequired: {{.IsRequired}}, IsJSON: {{.IsJSON}}{{with .Default}}, Default: {{.}}{{end}}}, {{.Target}}); err != nil { a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorParse, OperationID: {{$op.ID}}, ParamName: {{.Name}}, ParamLocation: {{.Location}}, Err: err}) - {{$.Handler.Return}} + return } {{- end}} {{- if .HasBody}} @@ -148,7 +167,7 @@ func (a *HTTPAdapter) {{.Name}}{{$.Handler.Signature}} { case {{.Empty}}: {{- if .IsBodyRequired}} a.failDecode(w, r, {{.ID}}, {{$.Runtime}}.ErrBodyEmpty) - {{$.Handler.Return}} + return {{- end}} {{- range .Patterns}} case {{.Case}}: @@ -159,14 +178,14 @@ func (a *HTTPAdapter) {{.Name}}{{$.Handler.Signature}} { {{- template "server/body.tmpl" .}} {{- else}} a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorDecode, OperationID: {{.ID}}, Status: {{$.HTTP}}.StatusUnsupportedMediaType, Err: {{$.Runtime}}.ContentTypeError(contentType)}) - {{$.Handler.Return}} + return {{- end}} } {{- end}} {{- if $.IsRequestValidated}} if err := opts.Validate(); err != nil { a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorValidation, OperationID: {{.ID}}, Err: err}) - {{$.Handler.Return}} + return } {{- end}} @@ -176,20 +195,17 @@ func (a *HTTPAdapter) {{.Name}}{{$.Handler.Signature}} { if e, ok := {{$.HTTPServer}}.AsError[{{.Type}}](err); ok { w.Header().Set("Content-Type", {{.MediaType}}) a.opts.ErrorHandler.HandleError(w, r, {{.Status}}, e) - {{$.Handler.Return}} + return } {{- end}} a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorService, OperationID: {{.ID}}, Err: err}) - {{$.Handler.Return}} + return } if res == nil { a.fail(w, r, &{{$.HTTPServer}}.HandlerError{Kind: {{$.HTTPServer}}.ErrorService, OperationID: {{.ID}}, Err: {{$.HTTPServer}}.ErrNoResponse}) - {{$.Handler.Return}} + return } a.write(w, r, {{.ID}}, res) -{{- if $.Handler.Epilogue}} - {{$.Handler.Epilogue}} -{{- end}} } {{- end}} diff --git a/internal/gen/server/templates/body.tmpl b/internal/gen/server/templates/body.tmpl index 564020a4..d7652869 100644 --- a/internal/gen/server/templates/body.tmpl +++ b/internal/gen/server/templates/body.tmpl @@ -2,16 +2,16 @@ body, err := a.opts.Presence.JSON(r.Body, {{.Runtime}}.Prop{{template "server/prop.tmpl" .Check}}) if err != nil { a.failBody(w, r, {{.OperationID}}, err) - {{.Return}} + return } if err = {{.Runtime}}.DecodeJSON(body, {{.Target}}, {{.IsRequired}}, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if .IsJSON}} if err := {{.Runtime}}.DecodeJSON(r.Body, {{.Target}}, {{.IsRequired}}, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if and .IsForm .Check}} {{- with .EncodingLiteral}} @@ -20,11 +20,11 @@ body, err := a.opts.Presence.Form(r.Body, {{.Runtime}}.Prop{{template "server/prop.tmpl" .Check}}, {{.Encoding}}) if err != nil { a.failBody(w, r, {{.OperationID}}, err) - {{.Return}} + return } if err = {{.Runtime}}.DecodeForm(body, {{.Target}}, {{.IsRequired}}, {{.Encoding}}); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if .IsForm}} {{- with .EncodingLiteral}} @@ -32,7 +32,7 @@ {{- end}} if err := {{.Runtime}}.DecodeForm(r.Body, {{.Target}}, {{.IsRequired}}, {{.Encoding}}); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if .IsMultipart}} {{- with .EncodingLiteral}} @@ -41,38 +41,38 @@ {{- with .Check}} if err := a.opts.Presence.Multipart(r, {{.Runtime}}.Prop{{template "server/prop.tmpl" .}}, a.opts.MultipartMaxMemory, {{$.Encoding}}); err != nil { a.failBody(w, r, {{$.OperationID}}, err) - {{$.Return}} + return } {{- end}} opts.{{.Field}} = &{{.Type}}{} if err := {{.Runtime}}.DecodeMultipart(r, opts.{{.Field}}, a.opts.MultipartMaxMemory, {{.Encoding}}); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if .IsFile}} file, err := {{.Runtime}}.DecodeFile(r, {{.IsRequired}}) if err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } opts.{{.Field}} = {{.Assign}} {{- else if .IsText}} text, err := {{.Runtime}}.DecodeText(r.Body, {{.IsRequired}}) if err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } opts.{{.Field}} = {{.Assign}} {{- else if .IsValue}} if err := {{.Runtime}}.DecodeTextValue(r.Body, {{.Target}}, {{.IsRequired}}); err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } {{- else if .IsBytes}} data, err := {{.Runtime}}.DecodeBytes(r.Body, {{.IsRequired}}) if err != nil { a.failDecode(w, r, {{.OperationID}}, err) - {{.Return}} + return } opts.{{.Field}} = {{.Assign}} {{- end}} \ No newline at end of file diff --git a/internal/gen/server/testdata/server.adapter.echo-v5.golden b/internal/gen/server/testdata/server.adapter.echo-v5.golden index 1ec3e981..251b744d 100644 --- a/internal/gen/server/testdata/server.adapter.echo-v5.golden +++ b/internal/gen/server/testdata/server.adapter.echo-v5.golden @@ -12,12 +12,13 @@ import ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -44,6 +45,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -74,8 +82,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -87,37 +96,41 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "filter", Style: runtime.StyleDeepObject, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "filter", ParamLocation: "query", Err: err}) - return nil + return } opts.Headers = &ListPetsHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Trace", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: true, Default: `"none"`}, &opts.Headers.XTrace); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "X-Trace", ParamLocation: "header", Err: err}) - return nil + return } opts.Cookies = &ListPetsCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "ListPets", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) @@ -125,73 +138,75 @@ func (a *HTTPAdapter) ListPets(c *echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 500, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.BodyJSON, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.BodyForm, true, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "multipart/form-data": opts.BodyMultipart = &Upload{} if err := runtime.DecodeMultipart(r, opts.BodyMultipart, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "text/plain": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyText = text case "text/markdown": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyMarkdown = runtime.Ptr(Note(text)) case "image/png": data, err := runtime.DecodeBytes(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyPng = data case "application/xml": case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "CreatePet", Err: err}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) @@ -199,47 +214,51 @@ func (a *HTTPAdapter) CreatePet(c *echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c *echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false, Default: `{"name":"all"}`}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "DeletePet", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeletePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "DeletePet", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) @@ -247,55 +266,56 @@ func (a *HTTPAdapter) DeletePet(c *echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c *echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "body", IsRequired: true, IsJSON: true}, &opts.BodyQueryString); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Ping", ParamName: "body", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "Ping", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Ping", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "Ping", Err: err}) - return nil + return } res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/internal/gen/server/testdata/server.adapter.echo.golden b/internal/gen/server/testdata/server.adapter.echo.golden index 34e34f12..ca2b994b 100644 --- a/internal/gen/server/testdata/server.adapter.echo.golden +++ b/internal/gen/server/testdata/server.adapter.echo.golden @@ -12,12 +12,13 @@ import ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -44,6 +45,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -74,8 +82,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -87,37 +96,41 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "filter", Style: runtime.StyleDeepObject, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "filter", ParamLocation: "query", Err: err}) - return nil + return } opts.Headers = &ListPetsHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Trace", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: true, Default: `"none"`}, &opts.Headers.XTrace); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "X-Trace", ParamLocation: "header", Err: err}) - return nil + return } opts.Cookies = &ListPetsCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "ListPets", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) @@ -125,73 +138,75 @@ func (a *HTTPAdapter) ListPets(c echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 500, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.BodyJSON, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.BodyForm, true, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "multipart/form-data": opts.BodyMultipart = &Upload{} if err := runtime.DecodeMultipart(r, opts.BodyMultipart, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "text/plain": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyText = text case "text/markdown": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyMarkdown = runtime.Ptr(Note(text)) case "image/png": data, err := runtime.DecodeBytes(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyPng = data case "application/xml": case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "CreatePet", Err: err}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) @@ -199,47 +214,51 @@ func (a *HTTPAdapter) CreatePet(c echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c echo.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, c.Param("id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false, Default: `{"name":"all"}`}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "DeletePet", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeletePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "DeletePet", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) @@ -247,55 +266,56 @@ func (a *HTTPAdapter) DeletePet(c echo.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c echo.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "body", IsRequired: true, IsJSON: true}, &opts.BodyQueryString); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Ping", ParamName: "body", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "Ping", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Ping", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "Ping", Err: err}) - return nil + return } res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/internal/gen/server/testdata/server.adapter.golden b/internal/gen/server/testdata/server.adapter.golden index 1f57a6ba..3f029810 100644 --- a/internal/gen/server/testdata/server.adapter.golden +++ b/internal/gen/server/testdata/server.adapter.golden @@ -12,12 +12,13 @@ import ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -44,6 +45,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -74,8 +82,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -87,12 +96,16 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(w, r, "ListPets", a.serveListPets) +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} @@ -138,7 +151,10 @@ func (a *HTTPAdapter) ListPets(w http.ResponseWriter, r *http.Request) { // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(w, r, "CreatePet", a.serveCreatePet) +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": @@ -210,7 +226,10 @@ func (a *HTTPAdapter) CreatePet(w http.ResponseWriter, r *http.Request) { // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(w, r, "DeletePet", a.serveDeletePet) +} + +func (a *HTTPAdapter) serveDeletePet(w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(runtime.UnescapePath(r, chi.URLParam(r, "id")), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { @@ -256,7 +275,10 @@ func (a *HTTPAdapter) DeletePet(w http.ResponseWriter, r *http.Request) { // Ping handles GET /ping. func (a *HTTPAdapter) Ping(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(w, r, "Ping", a.servePing) +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "body", IsRequired: true, IsJSON: true}, &opts.BodyQueryString); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Ping", ParamName: "body", ParamLocation: "querystring", Err: err}) diff --git a/internal/gen/server/testdata/server.adapter.kratos.golden b/internal/gen/server/testdata/server.adapter.kratos.golden index f035ed91..1ffc2560 100644 --- a/internal/gen/server/testdata/server.adapter.kratos.golden +++ b/internal/gen/server/testdata/server.adapter.kratos.golden @@ -12,12 +12,13 @@ import ( // ServerOptions is what the adapter and the router are set up with. type ServerOptions struct { - Router any - Middleware []func(http.Handler) http.Handler - ErrorHandler httpserver.ErrorHandler - JSONDecoder func(data []byte, v any) error - JSONEncoder func(v any) ([]byte, error) - MultipartMaxMemory int64 + Router any + Middleware []func(http.Handler) http.Handler + OperationMiddleware []func(http.Handler) http.Handler + ErrorHandler httpserver.ErrorHandler + JSONDecoder func(data []byte, v any) error + JSONEncoder func(v any) ([]byte, error) + MultipartMaxMemory int64 } // ServerOption sets one field of ServerOptions. @@ -44,6 +45,13 @@ func WithMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { } } +// WithOperationMiddleware wraps each operation with mw, once its name is on the context. +func WithOperationMiddleware(mw ...func(http.Handler) http.Handler) ServerOption { + return func(o *ServerOptions) { + o.OperationMiddleware = append(o.OperationMiddleware, mw...) + } +} + // WithErrorHandler sets what writes the response of a failed request. func WithErrorHandler(h httpserver.ErrorHandler) ServerOption { return func(o *ServerOptions) { @@ -74,8 +82,9 @@ func WithMultipartMaxMemory(n int64) ServerOption { // HTTPAdapter answers HTTP requests by calling the service. type HTTPAdapter struct { - svc PetsInterface - opts *ServerOptions + svc PetsInterface + opts *ServerOptions + operations httpserver.Chain } type responseData interface { @@ -87,37 +96,41 @@ type responseData interface { // NewHTTPAdapter returns the adapter of svc. func NewHTTPAdapter(svc PetsInterface, opts ...ServerOption) *HTTPAdapter { - return &HTTPAdapter{svc: svc, opts: NewServerOptions(opts...)} + o := NewServerOptions(opts...) + return &HTTPAdapter{svc: svc, opts: o, operations: httpserver.NewChain(o.OperationMiddleware...)} } // ListPets handles GET /pets. func (a *HTTPAdapter) ListPets(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "ListPets")) + a.operations.Serve(c.Response(), c.Request(), "ListPets", a.serveListPets) + return nil +} + +func (a *HTTPAdapter) serveListPets(w http.ResponseWriter, r *http.Request) { opts := &ListPetsServiceRequestOptions{RawRequest: r} query := runtime.ParseQuery(r.URL.RawQuery) opts.Query = &ListPetsQuery{} if err := runtime.DecodeQuery(query, runtime.Param{Name: "limit", Style: runtime.StyleForm, IsExplode: true, IsRequired: false, IsJSON: false, Default: "20"}, &opts.Query.Limit); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "limit", ParamLocation: "query", Err: err}) - return nil + return } if err := runtime.DecodeQuery(query, runtime.Param{Name: "filter", Style: runtime.StyleDeepObject, IsExplode: true, IsRequired: true, IsJSON: false}, &opts.Query.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "filter", ParamLocation: "query", Err: err}) - return nil + return } opts.Headers = &ListPetsHeaders{} if err := runtime.DecodeHeader(r.Header, runtime.Param{Name: "X-Trace", Style: runtime.StyleSimple, IsExplode: false, IsRequired: false, IsJSON: true, Default: `"none"`}, &opts.Headers.XTrace); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "X-Trace", ParamLocation: "header", Err: err}) - return nil + return } opts.Cookies = &ListPetsCookies{} if err := runtime.DecodeCookie(r.Cookies(), runtime.Param{Name: "session", Style: runtime.StyleForm, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.Cookies.Session); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "ListPets", ParamName: "session", ParamLocation: "cookie", Err: err}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "ListPets", Err: err}) - return nil + return } res, err := a.svc.ListPets(r.Context(), opts) @@ -125,73 +138,75 @@ func (a *HTTPAdapter) ListPets(c khttp.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 500, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "ListPets", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "ListPets", res) - return nil } // CreatePet handles POST /pets. func (a *HTTPAdapter) CreatePet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "CreatePet")) + a.operations.Serve(c.Response(), c.Request(), "CreatePet", a.serveCreatePet) + return nil +} + +func (a *HTTPAdapter) serveCreatePet(w http.ResponseWriter, r *http.Request) { opts := &CreatePetServiceRequestOptions{RawRequest: r} switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.BodyJSON, true, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "application/x-www-form-urlencoded": if err := runtime.DecodeForm(r.Body, &opts.BodyForm, true, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "multipart/form-data": opts.BodyMultipart = &Upload{} if err := runtime.DecodeMultipart(r, opts.BodyMultipart, a.opts.MultipartMaxMemory, nil); err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } case "text/plain": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyText = text case "text/markdown": text, err := runtime.DecodeText(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyMarkdown = runtime.Ptr(Note(text)) case "image/png": data, err := runtime.DecodeBytes(r.Body, true) if err != nil { a.failDecode(w, r, "CreatePet", err) - return nil + return } opts.BodyPng = data case "application/xml": case "": a.failDecode(w, r, "CreatePet", runtime.ErrBodyEmpty) - return nil + return default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "CreatePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "CreatePet", Err: err}) - return nil + return } res, err := a.svc.CreatePet(r.Context(), opts) @@ -199,47 +214,51 @@ func (a *HTTPAdapter) CreatePet(c khttp.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/problem+json") a.opts.ErrorHandler.HandleError(w, r, 400, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "CreatePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "CreatePet", res) - return nil } // DeletePet handles DELETE /pets/{id}. func (a *HTTPAdapter) DeletePet(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "DeletePet")) + a.operations.Serve(c.Response(), c.Request(), "DeletePet", func(w http.ResponseWriter, r *http.Request) { + a.serveDeletePet(c, w, r) + }) + return nil +} + +func (a *HTTPAdapter) serveDeletePet(c khttp.Context, w http.ResponseWriter, r *http.Request) { opts := &DeletePetServiceRequestOptions{RawRequest: r} opts.PathParams = &DeletePetPathParams{} if err := runtime.DecodePath(c.Vars().Get("id"), runtime.Param{Name: "id", Style: runtime.StyleSimple, IsExplode: false, IsRequired: true, IsJSON: false}, &opts.PathParams.ID); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "id", ParamLocation: "path", Err: err}) - return nil + return } if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "filter", IsRequired: false, IsJSON: false, Default: `{"name":"all"}`}, &opts.Filter); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "DeletePet", ParamName: "filter", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "DeletePet", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "DeletePet", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "DeletePet", Err: err}) - return nil + return } res, err := a.svc.DeletePet(r.Context(), opts) @@ -247,55 +266,56 @@ func (a *HTTPAdapter) DeletePet(c khttp.Context) error { if e, ok := httpserver.AsError[Problem](err); ok { w.Header().Set("Content-Type", "application/json") a.opts.ErrorHandler.HandleError(w, r, 404, e) - return nil + return } a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "DeletePet", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "DeletePet", res) - return nil } // Ping handles GET /ping. func (a *HTTPAdapter) Ping(c khttp.Context) error { - w, r := c.Response(), c.Request() - r = r.WithContext(runtime.WithOperationID(r.Context(), "Ping")) + a.operations.Serve(c.Response(), c.Request(), "Ping", a.servePing) + return nil +} + +func (a *HTTPAdapter) servePing(w http.ResponseWriter, r *http.Request) { opts := &PingServiceRequestOptions{RawRequest: r} if err := runtime.DecodeQueryString(r.URL.RawQuery, runtime.Param{Name: "body", IsRequired: true, IsJSON: true}, &opts.BodyQueryString); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorParse, OperationID: "Ping", ParamName: "body", ParamLocation: "querystring", Err: err}) - return nil + return } switch contentType := runtime.ContentType(r.Header); contentType { case "application/json": if err := runtime.DecodeJSON(r.Body, &opts.Body, false, a.opts.JSONDecoder); err != nil { a.failDecode(w, r, "Ping", err) - return nil + return } case "": default: a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorDecode, OperationID: "Ping", Status: http.StatusUnsupportedMediaType, Err: runtime.ContentTypeError(contentType)}) - return nil + return } if err := opts.Validate(); err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorValidation, OperationID: "Ping", Err: err}) - return nil + return } res, err := a.svc.Ping(r.Context(), opts) if err != nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: err}) - return nil + return } if res == nil { a.fail(w, r, &httpserver.HandlerError{Kind: httpserver.ErrorService, OperationID: "Ping", Err: httpserver.ErrNoResponse}) - return nil + return } a.write(w, r, "Ping", res) - return nil } func (a *HTTPAdapter) fail(w http.ResponseWriter, r *http.Request, err *httpserver.HandlerError) { diff --git a/internal/gomodel/model.go b/internal/gomodel/model.go index a89e5f62..c8825ece 100644 --- a/internal/gomodel/model.go +++ b/internal/gomodel/model.go @@ -23,7 +23,7 @@ import ( // serverNames are what the server parts declare next to the models. var serverNames = []string{ "NewRouter", "HTTPAdapter", "NewHTTPAdapter", "ServerOption", "ServerOptions", "NewServerOptions", - "WithRouter", "WithMiddleware", "WithErrorHandler", "WithJSONDecoder", "WithJSONEncoder", "WithMultipartMaxMemory", "WithPresence", + "WithRouter", "WithMiddleware", "WithOperationMiddleware", "WithErrorHandler", "WithJSONDecoder", "WithJSONEncoder", "WithMultipartMaxMemory", "WithPresence", "ErrorKind", "HandlerError", "ErrorHandler", "ErrorHandlerFunc", "DefaultErrorHandler", "ErrorParse", "ErrorDecode", "ErrorValidation", "ErrorService", "ErrorResponse", } diff --git a/pkg/runtime/httpserver/chain.go b/pkg/runtime/httpserver/chain.go new file mode 100644 index 00000000..7248a3d1 --- /dev/null +++ b/pkg/runtime/httpserver/chain.go @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Mockzilla +// SPDX-License-Identifier: MIT +// Licensed under the MIT License, see LICENSE in the repository root. This copyright notice and +// permission notice shall be included in all copies or substantial portions of the Software. + +// Middleware that runs inside a handler of a generated server, once its operation is known. + +package httpserver + +import ( + "context" + "net/http" + "slices" + + "github.com/mockzilla/mockzilla-codegen/pkg/runtime" +) + +type serveKey struct{} + +// Chain is middleware built once, which each request runs its own handler inside. +type Chain struct { + handler http.Handler +} + +// NewChain wraps mw around the handler each request carries, outermost first. +func NewChain(mw ...func(http.Handler) http.Handler) Chain { + if len(mw) == 0 { + return Chain{} + } + + var h http.Handler = http.HandlerFunc(serveCarried) + for _, m := range slices.Backward(mw) { + h = m(h) + } + return Chain{handler: h} +} + +// Serve puts id on the request's context as the operation name, then runs serve inside the +// middleware. +func (c Chain) Serve(w http.ResponseWriter, r *http.Request, id string, serve http.HandlerFunc) { + ctx := runtime.WithOperationID(r.Context(), id) + if c.handler == nil { + serve(w, r.WithContext(ctx)) + return + } + // The middleware is built once, so the handler of this request travels on its context. + c.handler.ServeHTTP(w, r.WithContext(context.WithValue(ctx, serveKey{}, serve))) +} + +func serveCarried(w http.ResponseWriter, r *http.Request) { + serve, _ := r.Context().Value(serveKey{}).(http.HandlerFunc) + if serve == nil { + panic(ErrContextLost) + } + serve(w, r) +} diff --git a/pkg/runtime/httpserver/chain_test.go b/pkg/runtime/httpserver/chain_test.go new file mode 100644 index 00000000..f3b1750d --- /dev/null +++ b/pkg/runtime/httpserver/chain_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2026 Mockzilla +// SPDX-License-Identifier: MIT +// Licensed under the MIT License, see LICENSE in the repository root. This copyright notice and +// permission notice shall be included in all copies or substantial portions of the Software. + +package httpserver + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mockzilla/mockzilla-codegen/pkg/runtime" +) + +type tagKey struct{} + +func TestChainServe(t *testing.T) { + t.Parallel() + + var built int + var seen []string + tag := func(name string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + built++ + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + seen = append(seen, name+" "+runtime.OperationID(r.Context())) + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), tagKey{}, name))) + }) + } + } + chain := NewChain(tag("outer"), tag("inner")) + serve := func(w http.ResponseWriter, r *http.Request) { + seen = append(seen, "serve "+runtime.OperationID(r.Context())+" "+r.Context().Value(tagKey{}).(string)) + w.WriteHeader(http.StatusAccepted) + } + + for _, id := range []string{"ListPets", "GetPet"} { + rec := httptest.NewRecorder() + chain.Serve(rec, httptest.NewRequest(http.MethodGet, "/", nil), id, serve) + assert.Equal(t, http.StatusAccepted, rec.Code) + } + + assert.Equal(t, 2, built, "each middleware is built once") + assert.Equal(t, []string{ + "outer ListPets", "inner ListPets", "serve ListPets inner", + "outer GetPet", "inner GetPet", "serve GetPet inner", + }, seen) +} + +func TestChainServeWithoutMiddleware(t *testing.T) { + t.Parallel() + + var got string + rec := httptest.NewRecorder() + + NewChain().Serve(rec, httptest.NewRequest(http.MethodGet, "/", nil), "ListPets", func(_ http.ResponseWriter, r *http.Request) { + got = runtime.OperationID(r.Context()) + }) + + assert.Equal(t, "ListPets", got) +} + +func TestChainServeContextLost(t *testing.T) { + t.Parallel() + + detach := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r.WithContext(context.Background())) + }) + } + chain := NewChain(detach) + + assert.PanicsWithValue(t, ErrContextLost, func() { + chain.Serve(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil), "ListPets", func(http.ResponseWriter, *http.Request) {}) + }) +} diff --git a/pkg/runtime/httpserver/errors.go b/pkg/runtime/httpserver/errors.go index cb5633ce..e67f2056 100644 --- a/pkg/runtime/httpserver/errors.go +++ b/pkg/runtime/httpserver/errors.go @@ -11,4 +11,5 @@ var ( ErrNoResponse = errors.New("the service returned no response") ErrResponseCut = errors.New("response cut short") ErrPanic = errors.New("panic") + ErrContextLost = errors.New("a middleware passed on a request without the context it got") ) diff --git a/pkg/runtime/httpserver/httpserver.go b/pkg/runtime/httpserver/httpserver.go index 468d066a..2112c65c 100644 --- a/pkg/runtime/httpserver/httpserver.go +++ b/pkg/runtime/httpserver/httpserver.go @@ -3,7 +3,8 @@ // Licensed under the MIT License, see LICENSE in the repository root. This copyright notice and // permission notice shall be included in all copies or substantial portions of the Software. -// Package httpserver holds the errors, error handlers and response writing of a generated server. +// Package httpserver holds the errors, error handlers, operation middleware and response writing +// of a generated server. package httpserver import (