From 3582ae5d67ba092a3b70ee40971f00bf3f3f1d06 Mon Sep 17 00:00:00 2001 From: chaseisabelle Date: Sat, 30 Aug 2025 11:52:01 -0500 Subject: [PATCH] feature(std-http-server): option to inject request metadata into context --- .../authenticated-api/stdhttp/api/api.gen.go | 68 +++++++-- .../stdhttp-go-tool/api/ping.gen.go | 62 ++++++-- .../minimal-server/stdhttp/api/ping.gen.go | 62 ++++++-- .../stdhttp/api/petstore.gen.go | 83 +++++++++-- .../test/strict-server/stdhttp/server.gen.go | 138 ++++++++++++++++-- internal/test/strict-server/stdhttp/server.go | 89 ++++++++++- .../strict-server/stdhttp/std_strict_test.go | 38 +++++ .../templates/stdhttp/std-http-handler.tmpl | 16 +- .../stdhttp/std-http-middleware.tmpl | 46 +++++- 9 files changed, 541 insertions(+), 61 deletions(-) diff --git a/examples/authenticated-api/stdhttp/api/api.gen.go b/examples/authenticated-api/stdhttp/api/api.gen.go index 91ced48c3d..579d47dc62 100644 --- a/examples/authenticated-api/stdhttp/api/api.gen.go +++ b/examples/authenticated-api/stdhttp/api/api.gen.go @@ -419,15 +419,54 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + // ListThings operation middleware func (siw *ServerInterfaceWrapper) ListThings(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "ListThings", + RequestRoute: "/things", + })) + } ctx := r.Context() @@ -448,6 +487,13 @@ func (siw *ServerInterfaceWrapper) ListThings(w http.ResponseWriter, r *http.Req // AddThing operation middleware func (siw *ServerInterfaceWrapper) AddThing(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "AddThing", + RequestRoute: "/things", + })) + } ctx := r.Context() @@ -547,10 +593,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -581,9 +628,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } m.HandleFunc("GET "+options.BaseURL+"/things", wrapper.ListThings) diff --git a/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go b/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go index 794f8d817f..dfb3a2fe12 100644 --- a/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go +++ b/examples/minimal-server/stdhttp-go-tool/api/ping.gen.go @@ -6,6 +6,7 @@ package api import ( + "context" "fmt" "net/http" ) @@ -24,15 +25,54 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + // GetPing operation middleware func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "GetPing", + RequestRoute: "/ping", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetPing(w, r) @@ -126,10 +166,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -160,9 +201,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } m.HandleFunc("GET "+options.BaseURL+"/ping", wrapper.GetPing) diff --git a/examples/minimal-server/stdhttp/api/ping.gen.go b/examples/minimal-server/stdhttp/api/ping.gen.go index 794f8d817f..dfb3a2fe12 100644 --- a/examples/minimal-server/stdhttp/api/ping.gen.go +++ b/examples/minimal-server/stdhttp/api/ping.gen.go @@ -6,6 +6,7 @@ package api import ( + "context" "fmt" "net/http" ) @@ -24,15 +25,54 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + // GetPing operation middleware func (siw *ServerInterfaceWrapper) GetPing(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "GetPing", + RequestRoute: "/ping", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetPing(w, r) @@ -126,10 +166,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -160,9 +201,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } m.HandleFunc("GET "+options.BaseURL+"/ping", wrapper.GetPing) diff --git a/examples/petstore-expanded/stdhttp/api/petstore.gen.go b/examples/petstore-expanded/stdhttp/api/petstore.gen.go index 3f99776250..68ff4fe8d2 100644 --- a/examples/petstore-expanded/stdhttp/api/petstore.gen.go +++ b/examples/petstore-expanded/stdhttp/api/petstore.gen.go @@ -8,6 +8,7 @@ package api import ( "bytes" "compress/gzip" + "context" "encoding/base64" "fmt" "net/http" @@ -79,15 +80,54 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + // FindPets operation middleware func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "FindPets", + RequestRoute: "/pets", + })) + } var err error @@ -123,6 +163,13 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque // AddPet operation middleware func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "AddPet", + RequestRoute: "/pets", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AddPet(w, r) @@ -137,6 +184,13 @@ func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request // DeletePet operation middleware func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "DeletePet", + RequestRoute: "/pets/{id}", + })) + } var err error @@ -162,6 +216,13 @@ func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Requ // FindPetByID operation middleware func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "FindPetByID", + RequestRoute: "/pets/{id}", + })) + } var err error @@ -266,10 +327,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -300,9 +362,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } m.HandleFunc("GET "+options.BaseURL+"/pets", wrapper.FindPets) diff --git a/internal/test/strict-server/stdhttp/server.gen.go b/internal/test/strict-server/stdhttp/server.gen.go index 28c36a7ac7..008a24e233 100644 --- a/internal/test/strict-server/stdhttp/server.gen.go +++ b/internal/test/strict-server/stdhttp/server.gen.go @@ -67,15 +67,54 @@ type ServerInterface interface { // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + // JSONExample operation middleware func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "JSONExample", + RequestRoute: "/json", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.JSONExample(w, r) @@ -90,6 +129,13 @@ func (siw *ServerInterfaceWrapper) JSONExample(w http.ResponseWriter, r *http.Re // MultipartExample operation middleware func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "MultipartExample", + RequestRoute: "/multipart", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipartExample(w, r) @@ -104,6 +150,13 @@ func (siw *ServerInterfaceWrapper) MultipartExample(w http.ResponseWriter, r *ht // MultipartRelatedExample operation middleware func (siw *ServerInterfaceWrapper) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "MultipartRelatedExample", + RequestRoute: "/multipart-related", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipartRelatedExample(w, r) @@ -118,6 +171,13 @@ func (siw *ServerInterfaceWrapper) MultipartRelatedExample(w http.ResponseWriter // MultipleRequestAndResponseTypes operation middleware func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "MultipleRequestAndResponseTypes", + RequestRoute: "/multiple", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.MultipleRequestAndResponseTypes(w, r) @@ -132,6 +192,13 @@ func (siw *ServerInterfaceWrapper) MultipleRequestAndResponseTypes(w http.Respon // ReservedGoKeywordParameters operation middleware func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "ReservedGoKeywordParameters", + RequestRoute: "/reserved-go-keyword-parameters/{type}", + })) + } var err error @@ -157,6 +224,13 @@ func (siw *ServerInterfaceWrapper) ReservedGoKeywordParameters(w http.ResponseWr // ReusableResponses operation middleware func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "ReusableResponses", + RequestRoute: "/reusable-responses", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ReusableResponses(w, r) @@ -171,6 +245,13 @@ func (siw *ServerInterfaceWrapper) ReusableResponses(w http.ResponseWriter, r *h // TextExample operation middleware func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "TextExample", + RequestRoute: "/text", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.TextExample(w, r) @@ -185,6 +266,13 @@ func (siw *ServerInterfaceWrapper) TextExample(w http.ResponseWriter, r *http.Re // UnknownExample operation middleware func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "UnknownExample", + RequestRoute: "/unknown", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnknownExample(w, r) @@ -199,6 +287,13 @@ func (siw *ServerInterfaceWrapper) UnknownExample(w http.ResponseWriter, r *http // UnspecifiedContentType operation middleware func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "UnspecifiedContentType", + RequestRoute: "/unspecified-content-type", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnspecifiedContentType(w, r) @@ -213,6 +308,13 @@ func (siw *ServerInterfaceWrapper) UnspecifiedContentType(w http.ResponseWriter, // URLEncodedExample operation middleware func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "URLEncodedExample", + RequestRoute: "/urlencoded", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.URLEncodedExample(w, r) @@ -227,6 +329,13 @@ func (siw *ServerInterfaceWrapper) URLEncodedExample(w http.ResponseWriter, r *h // HeadersExample operation middleware func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "HeadersExample", + RequestRoute: "/with-headers", + })) + } var err error @@ -290,6 +399,13 @@ func (siw *ServerInterfaceWrapper) HeadersExample(w http.ResponseWriter, r *http // UnionExample operation middleware func (siw *ServerInterfaceWrapper) UnionExample(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "UnionExample", + RequestRoute: "/with-union", + })) + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UnionExample(w, r) @@ -383,10 +499,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -417,9 +534,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } m.HandleFunc("POST "+options.BaseURL+"/json", wrapper.JSONExample) diff --git a/internal/test/strict-server/stdhttp/server.go b/internal/test/strict-server/stdhttp/server.go index a142a6b813..595802f62b 100644 --- a/internal/test/strict-server/stdhttp/server.go +++ b/internal/test/strict-server/stdhttp/server.go @@ -10,9 +10,24 @@ import ( "encoding/json" "io" "mime/multipart" + "net/http" ) -type StrictServer struct { +type StrictServer struct{} + +type MockServer struct { + JSONExampleMock func(w http.ResponseWriter, r *http.Request) + MultipartExampleMock func(w http.ResponseWriter, r *http.Request) + MultipartRelatedExampleMock func(w http.ResponseWriter, r *http.Request) + MultipleRequestAndResponseTypesMock func(w http.ResponseWriter, r *http.Request) + ReservedGoKeywordParametersMock func(w http.ResponseWriter, r *http.Request, pType string) + ReusableResponsesMock func(w http.ResponseWriter, r *http.Request) + TextExampleMock func(w http.ResponseWriter, r *http.Request) + UnknownExampleMock func(w http.ResponseWriter, r *http.Request) + UnspecifiedContentTypeMock func(w http.ResponseWriter, r *http.Request) + URLEncodedExampleMock func(w http.ResponseWriter, r *http.Request) + HeadersExampleMock func(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) + UnionExampleMock func(w http.ResponseWriter, r *http.Request) } func (s StrictServer) JSONExample(ctx context.Context, request JSONExampleRequestObject) (JSONExampleResponseObject, error) { @@ -144,3 +159,75 @@ func (s StrictServer) UnionExample(ctx context.Context, request UnionExampleRequ }, }, nil } + +func (m *MockServer) JSONExample(w http.ResponseWriter, r *http.Request) { + if m.JSONExampleMock != nil { + m.JSONExampleMock(w, r) + } +} + +func (m *MockServer) MultipartExample(w http.ResponseWriter, r *http.Request) { + if m.MultipartExampleMock != nil { + m.MultipartExampleMock(w, r) + } +} + +func (m *MockServer) MultipartRelatedExample(w http.ResponseWriter, r *http.Request) { + if m.MultipartRelatedExampleMock != nil { + m.MultipartRelatedExampleMock(w, r) + } +} + +func (m *MockServer) MultipleRequestAndResponseTypes(w http.ResponseWriter, r *http.Request) { + if m.MultipleRequestAndResponseTypesMock != nil { + m.MultipleRequestAndResponseTypesMock(w, r) + } +} + +func (m *MockServer) ReservedGoKeywordParameters(w http.ResponseWriter, r *http.Request, pType string) { + if m.ReservedGoKeywordParametersMock != nil { + m.ReservedGoKeywordParametersMock(w, r, pType) + } +} + +func (m *MockServer) ReusableResponses(w http.ResponseWriter, r *http.Request) { + if m.ReusableResponsesMock != nil { + m.ReusableResponsesMock(w, r) + } +} + +func (m *MockServer) TextExample(w http.ResponseWriter, r *http.Request) { + if m.TextExampleMock != nil { + m.TextExampleMock(w, r) + } +} + +func (m *MockServer) UnknownExample(w http.ResponseWriter, r *http.Request) { + if m.UnknownExampleMock != nil { + m.UnknownExampleMock(w, r) + } +} + +func (m *MockServer) UnspecifiedContentType(w http.ResponseWriter, r *http.Request) { + if m.UnspecifiedContentTypeMock != nil { + m.UnspecifiedContentTypeMock(w, r) + } +} + +func (m *MockServer) URLEncodedExample(w http.ResponseWriter, r *http.Request) { + if m.URLEncodedExampleMock != nil { + m.URLEncodedExampleMock(w, r) + } +} + +func (m *MockServer) HeadersExample(w http.ResponseWriter, r *http.Request, params HeadersExampleParams) { + if m.HeadersExampleMock != nil { + m.HeadersExampleMock(w, r, params) + } +} + +func (m *MockServer) UnionExample(w http.ResponseWriter, r *http.Request) { + if m.UnionExampleMock != nil { + m.UnionExampleMock(w, r) + } +} diff --git a/internal/test/strict-server/stdhttp/std_strict_test.go b/internal/test/strict-server/stdhttp/std_strict_test.go index 40d362a54a..01a7fdae7d 100644 --- a/internal/test/strict-server/stdhttp/std_strict_test.go +++ b/internal/test/strict-server/stdhttp/std_strict_test.go @@ -9,6 +9,7 @@ import ( "mime" "mime/multipart" "net/http" + "net/http/httptest" "net/url" "strings" "testing" @@ -227,3 +228,40 @@ func testImpl(t *testing.T, handler http.Handler) { assert.Equal(t, requestBody, responseBody) }) } + +func TestStdHTTPServer_WithRequestMetadata(t *testing.T) { + han := HandlerWithOptions(&MockServer{ + JSONExampleMock: func(w http.ResponseWriter, r *http.Request) { + rmd := RequestMetadataFromRequest(r) + + if !assert.NotNilf(t, rmd, "RequestMetadataFromRequest should not be nil") { + return + } + + assert.Equalf(t, "JSONExample", rmd.OperationID, "OperationID should be JSONExample") + assert.Equalf(t, "/json", rmd.RequestRoute, "RequestRoute should be /json") + }, + }, StdHTTPServerOptions{ + WithRequestMetadata: true, + }) + + res := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/json", nil) + + han.ServeHTTP(res, req) + + assert.Equal(t, http.StatusOK, res.Code) + + han = HandlerWithOptions(&MockServer{ + JSONExampleMock: func(w http.ResponseWriter, r *http.Request) { + assert.Nilf(t, RequestMetadataFromRequest(r), "RequestMetadataFromRequest should not be nil") + }, + }, StdHTTPServerOptions{}) + + res = httptest.NewRecorder() + req = httptest.NewRequest(http.MethodPost, "/json", nil) + + han.ServeHTTP(res, req) + + assert.Equal(t, http.StatusOK, res.Code) +} diff --git a/pkg/codegen/templates/stdhttp/std-http-handler.tmpl b/pkg/codegen/templates/stdhttp/std-http-handler.tmpl index bed4685c74..c300c47ed3 100644 --- a/pkg/codegen/templates/stdhttp/std-http-handler.tmpl +++ b/pkg/codegen/templates/stdhttp/std-http-handler.tmpl @@ -10,10 +10,11 @@ type ServeMux interface { } type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + BaseURL string + BaseRouter ServeMux + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } // HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. @@ -44,9 +45,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } {{if .}} wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + WithRequestMetadata: options.WithRequestMetadata, } {{end}} {{range .}}m.HandleFunc("{{.Method }} "+options.BaseURL+"{{.Path | swaggerUriToStdHttpUri}}", wrapper.{{.OperationId}}) diff --git a/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl b/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl index 09977358c8..ee5c8a165f 100644 --- a/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl +++ b/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl @@ -1,16 +1,56 @@ // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + WithRequestMetadata bool } +type RequestMetadata struct { + OperationID string + RequestRoute string +} + +type contextKey string + type MiddlewareFunc func(http.Handler) http.Handler +var requestMetadataContextKey = contextKey("oapi-request-metadata") + +func RequestMetadataFromContext(ctx context.Context) *RequestMetadata { + if ctx == nil { + return nil + } + + rmd, ok := ctx.Value(requestMetadataContextKey).(*RequestMetadata) + + if ok { + return rmd + } + + return nil +} + +func RequestMetadataFromRequest(r *http.Request) *RequestMetadata { + if r == nil { + return nil + } + + return RequestMetadataFromContext(r.Context()) +} + {{range .}}{{$opid := .OperationId}} // {{$opid}} operation middleware func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { + // inject request metadata into the request context + if siw.WithRequestMetadata { + r = r.WithContext(context.WithValue(r.Context(), requestMetadataContextKey, &RequestMetadata{ + OperationID: "{{$opid}}", + RequestRoute: "{{.Path | swaggerUriToStdHttpUri}}", + })) + } + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} var err error {{end}}