mirror of
https://github.com/Fishwaldo/huma.git
synced 2025-03-15 19:31:27 +00:00
feat: get operation info from context
This commit is contained in:
parent
8ddda3bb3e
commit
fbeb18f3cf
4 changed files with 96 additions and 1 deletions
18
README.md
18
README.md
|
@ -682,6 +682,24 @@ middleware.NewLogger = func() (*zap.Logger, error) {
|
|||
}
|
||||
```
|
||||
|
||||
### Getting Operation Info
|
||||
|
||||
When setting up logging (or metrics, or auditing) you may want to have access to some additional information like the ID of the current operation. You can fetch this from the context **after** the handler has run.
|
||||
|
||||
```go
|
||||
app.Middleware(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// First, make sure the handler function runs!
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
// After that, you can get the operation info.
|
||||
opInfo := GetOperationInfo(r.Context())
|
||||
fmt.Println(opInfo.ID)
|
||||
fmt.Println(opInfo.URITemplate)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Lazy-loading at Server Startup
|
||||
|
||||
You can register functions to run before the server starts, allowing for things like lazy-loading dependencies. It is safe to call this method multiple times.
|
||||
|
|
31
operation.go
31
operation.go
|
@ -1,6 +1,7 @@
|
|||
package huma
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
@ -11,6 +12,29 @@ import (
|
|||
"github.com/danielgtaylor/huma/schema"
|
||||
)
|
||||
|
||||
// OperationInfo describes an operation. It contains useful information for
|
||||
// logging, metrics, auditing, etc.
|
||||
type OperationInfo struct {
|
||||
ID string
|
||||
URITemplate string
|
||||
Summary string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// GetOperationInfo returns information about the current Huma operation. This
|
||||
// will only be populated *after* routing has been handled, meaning *after*
|
||||
// `next.ServeHTTP(w, r)` has been called in your middleware.
|
||||
func GetOperationInfo(ctx context.Context) *OperationInfo {
|
||||
if oi := ctx.Value(opIDContextKey); oi != nil {
|
||||
return oi.(*OperationInfo)
|
||||
}
|
||||
|
||||
return &OperationInfo{
|
||||
ID: "unknown",
|
||||
Tags: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// Operation represents an operation (an HTTP verb, e.g. GET / PUT) against
|
||||
// a resource attached to a router.
|
||||
type Operation struct {
|
||||
|
@ -236,6 +260,13 @@ func (o *Operation) Run(handler interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update the operation info for loggers/metrics/etc middlware to use later.
|
||||
opInfo := GetOperationInfo(r.Context())
|
||||
opInfo.ID = o.id
|
||||
opInfo.URITemplate = o.resource.path
|
||||
opInfo.Summary = o.summary
|
||||
opInfo.Tags = append([]string{}, o.resource.tags...)
|
||||
|
||||
ctx := &hcontext{
|
||||
Context: r.Context(),
|
||||
ResponseWriter: w,
|
||||
|
|
10
router.go
10
router.go
|
@ -19,6 +19,10 @@ type contextKey string
|
|||
// context value.
|
||||
var connContextKey contextKey = "huma-request-conn"
|
||||
|
||||
// opIDContextKey is used to get the operation name after request routing
|
||||
// has finished.
|
||||
var opIDContextKey contextKey = "huma-operation-id"
|
||||
|
||||
// GetConn gets the underlying `net.Conn` from a context.
|
||||
func GetConn(ctx context.Context) net.Conn {
|
||||
conn := ctx.Value(connContextKey)
|
||||
|
@ -351,11 +355,15 @@ func New(docs, version string) *Router {
|
|||
ctx.WriteError(http.StatusMethodNotAllowed, fmt.Sprintf("No handler for method %s", r.Method))
|
||||
}))
|
||||
|
||||
// Automatically add links to OpenAPI and docs.
|
||||
r.Middleware(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// Inject the operation info before other middleware so that the later
|
||||
// middleware will have access to it.
|
||||
req = req.WithContext(context.WithValue(req.Context(), opIDContextKey, &OperationInfo{}))
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
|
||||
// Automatically add links to OpenAPI and docs.
|
||||
if req.URL.Path == "/" {
|
||||
link := w.Header().Get("link")
|
||||
if link != "" {
|
||||
|
|
|
@ -2,6 +2,7 @@ package huma
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -362,3 +363,40 @@ func TestCustomRequestSchema(t *testing.T) {
|
|||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
|
||||
}
|
||||
|
||||
func TestGetOperationName(t *testing.T) {
|
||||
app := newTestRouter()
|
||||
|
||||
var opInfo *OperationInfo
|
||||
app.Middleware(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
opInfo = GetOperationInfo(r.Context())
|
||||
})
|
||||
})
|
||||
|
||||
app.Resource("/").Get("test-id", "doc",
|
||||
NewResponse(http.StatusOK, "ok"),
|
||||
).Run(func(ctx Context) {
|
||||
// Do nothing!
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
app.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
assert.Equal(t, "test-id", opInfo.ID)
|
||||
assert.Equal(t, "/", opInfo.URITemplate)
|
||||
assert.Equal(t, "doc", opInfo.Summary)
|
||||
assert.Equal(t, []string{}, opInfo.Tags)
|
||||
}
|
||||
|
||||
func TestGetOperationDoesNotCrash(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
assert.NotPanics(t, func() {
|
||||
info := GetOperationInfo(ctx)
|
||||
assert.NotNil(t, info)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue