Skip to content

Commit

Permalink
Merge pull request #134 from Insei/middlewares
Browse files Browse the repository at this point in the history
huma: v2: add middlewares support
  • Loading branch information
danielgtaylor authored Oct 13, 2023
2 parents cc53278 + 19afc41 commit 076781b
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 5 deletions.
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,40 @@ http.ListenAndServe(":8888", r)
### Middleware

Huma v1 came with its own middleware, but v2 does not. You can use any middleware you want, or even write your own. This is for two reasons:
Huma v2 has support two variants of middlewares:

1. Middleware is often router-specific and Huma is designed to be router-agnostic.
2. Many organizations already have a set of middleware for logging, metrics, distributed tracing, panic recovery, etc.
1. Router-specific - works at the router level, i.e. before router-specific middleware, you can use any middleware that is implemented for your router.
2. Router-agnostic - runs in the Huma processing chain, i.e. after calls to router-specific middleware.

#### Router-specific
Each router implementation has its own middlewares, you can use this middlewares with huma v2 framework.

Chi router example:
```go
router := chi.NewMux()
router.Use(jwtauth.Verifier(tokenAuth))
api := humachi.New(router, defconfig)
```
> :whale: Huma v1 middleware is compatible with Chi, so if you use that router with v2 you can continue to use the v1 middleware in a v2 application.

#### Router-agnostic
You can write you own huma v2 middleware without dependency to router implementation.

Example:
```go
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
// I don't do anything
next(ctx)
}
func NewHumaAPI() huma.API {
// ...
api := humachi.New(router, config)
// OR api := humagin.New(router, config)
api.UseMiddleware(MyMiddleware)
}
```

## Open API Generation & Extensibility

Huma generates Open API 3.1.0 compatible JSON/YAML specs and provides rendered documentation automatically. Every operation that is registered with the API is included in the spec by default. The operation's inputs and outputs are used to generate the request and response parameters / schemas.
Expand Down
20 changes: 20 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ type API interface {

// Unmarshal unmarshals the given data into the given value. The content type
Unmarshal(contentType string, data []byte, v any) error

// UseMiddleware appends a middleware handler to the API middleware stack.
//
// The middleware stack for any API will execute before searching for a matching
// route to a specific handler, which provides opportunity to respond early,
// change the course of the request execution, or set request-scoped values for
// the next Middleware.
UseMiddleware(middlewares ...func(ctx Context, next func(Context)))

// Middlewares returns a slice of middleware handler functions.
Middlewares() Middlewares
}

// Format represents a request / response format. It is used to marshal and
Expand All @@ -141,6 +152,7 @@ type api struct {
formats map[string]Format
formatKeys []string
transformers []Transformer
middlewares Middlewares
}

func (a *api) Adapter() Adapter {
Expand Down Expand Up @@ -202,6 +214,14 @@ func (a *api) Marshal(ctx Context, respKey string, ct string, v any) error {
return f.Marshal(ctx.BodyWriter(), v)
}

func (a *api) UseMiddleware(middlewares ...func(ctx Context, next func(Context))) {
a.middlewares = append(a.middlewares, middlewares...)
}

func (a *api) Middlewares() Middlewares {
return a.middlewares
}

func NewAPI(config Config, a Adapter) API {
newAPI := &api{
config: config,
Expand Down
32 changes: 32 additions & 0 deletions chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package huma

type Middlewares []func(ctx Context, next func(Context))

// Handler builds and returns a handler func from the chain of middlewares,
// with `endpoint func` as the final handler.
func (m Middlewares) Handler(endpoint func(Context)) func(Context) {
return m.chain(endpoint)
}

// wrap user middleware func with the next func to one func
func wrap(fn func(Context, func(Context)), next func(Context)) func(Context) {
return func(ctx Context) {
fn(ctx, next)
}
}

// chain builds a Middleware composed of an inline middleware stack and endpoint
// handler in the order they are passed.
func (m Middlewares) chain(endpoint func(Context)) func(Context) {
// Return ahead of time if there aren't any middlewares for the chain
if len(m) == 0 {
return endpoint
}

// Wrap the end handler with the middleware chain
w := wrap(m[len(m)-1], endpoint)
for i := len(m) - 2; i >= 0; i-- {
w = wrap(m[i], w)
}
return w
}
4 changes: 2 additions & 2 deletions huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)

a := api.Adapter()

a.Handle(&op, func(ctx Context) {
a.Handle(&op, api.Middlewares().Handler(func(ctx Context) {
var input I

// Get the validation dependencies from the shared pool.
Expand Down Expand Up @@ -862,7 +862,7 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
} else {
ctx.SetStatus(status)
}
})
}))
}

// AutoRegister auto-detects operation registration methods and registers them
Expand Down

0 comments on commit 076781b

Please sign in to comment.