Skip to content

Commit

Permalink
add APIGen.ReplaceHumaAPI method and docs for it. update deps
Browse files Browse the repository at this point in the history
  • Loading branch information
cardinalby committed Nov 24, 2024
1 parent 7709ac0 commit 8dbc548
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Similar to other routers you can create a derived `api` (i.e. group) that has pr
- [**Base path**](./docs/base_path.md) (same as `Group`, `Route` methods in other routers)
- [**Multiple**](./docs/base_path.md) alternative **base paths**
- [**Middlewares**](./pkg/huma/op_handler/middlewares.go)
- including a [trick](./docs/huma_api_per_group.md) to use router-specific middlewares for a group
- [**Transformers**](./docs/transformers.md)
- [**Tags**](./pkg/huma/op_handler/add_tags.go) and [other](./pkg/huma/op_handler) Huma Operation properties
that will be applied to all endpoints in a group.
Expand Down
8 changes: 8 additions & 0 deletions api_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ func NewAPIGen(humaApi huma.API) APIGen {
}
}

// ReplaceHumaAPI returns a new APIGen instance with replaced huma.API pointer.
// Can be useful for some tricky cases when you create huma.API instance based on another adapter but with
// the same config (and OpenAPI object pointer) to utilize router-specific middlewares for a group.
func (a APIGen) ReplaceHumaAPI(humaApi huma.API) APIGen {
a.humaAPIWrapper = newHumaApiWrapper(humaApi)
return a
}

// GetHumaAPI returns the wrapped huma.API.
func (a APIGen) GetHumaAPI() huma.API {
return a.humaAPIWrapper
Expand Down
16 changes: 14 additions & 2 deletions api_gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ func testRegMiddleware(

func TestAPIGen_GetHumaAPI(t *testing.T) {
t.Parallel()
humaAPI := humago.New(http.NewServeMux(), huma.Config{})
api := NewAPIGen(humaAPI)
cfg := huma.DefaultConfig("test_api", "1.0.1")
humaAPI := humago.New(http.NewServeMux(), cfg)
api := NewAPIGen(humaAPI).AddRegMiddleware(func(op huma.Operation, next func(huma.Operation)) {
next(op)
})
require.Same(t, humaAPI, api.GetHumaAPI().(humaApiWrapper).API)
require.Len(t, api.GetRegMiddlewares(), 1)

cfg.OpenAPIPath = ""
cfg.DocsPath = ""
cfg.SchemasPath = ""
humaAPI2 := humago.New(http.NewServeMux(), cfg)
api2 := api.ReplaceHumaAPI(humaAPI2)
require.NotSame(t, api.GetHumaAPI().(humaApiWrapper).API, api2.GetHumaAPI().(humaApiWrapper).API)
require.Len(t, api.GetRegMiddlewares(), 1)
}

func TestAPIGen_GetRegMiddlewares(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@

- [Extended operation metadata](./metadata.md)
- [Per-group Transformers](./transformers.md)
- [OpenAPI endpoints](./openapi_endpoints.md) and multiple scoped specs
- [OpenAPI endpoints](./openapi_endpoints.md) and multiple scoped specs
- [Use a dedicated `huma.API` for a group](./huma_api_per_group.md) to utilize router-specific middlewares for a group
57 changes: 57 additions & 0 deletions docs/huma_api_per_group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Use a dedicated `huma.API` for a group

## Recap

HUMA doesn't allow you to use [router-specific middlewares](https://huma.rocks/features/middleware/) for
individual operations, you can only assign them to your router before creating `huma.API` instance.
Therefore, they will be applied to all operations registered with this `huma.API` instance.

This way, HUMA pushes you towards using "router-agnostic" own middleware format.

## The problem
However, sometimes you already have some middlewares compatible with your router and want to re-use them.

## The solution

Hureg allows you to define a dedicated `huma.API` instance (based on router's group) for a group of operations
using `APIGen.ReplaceHumaAPI()` method.

### Example

Let's say we have a simple setup with **chi router** and "v1" group defined using Hureg:
```go
mux := chi.NewRouter()

cfg := huma.DefaultConfig("My API", "1.0.0")
humaApi := humachi.New(mux, cfg)
api := hureg.NewAPIGen(humaApi)

v1api := api.AddBasePath("/v1")
hureg.Get(v1api, "/cat", catHandler) // GET /v1/cat
```

Now we want to add **"dog"** endpoint that will use chi's `middleware.Logger`:
```go
loggedMux := mux.With(middleware.Logger) // chi group with logger middleware

loggedCfg := cfg // The key point here is using the same OpenAPI pointer from the original cfg
loggedCfg.OpenAPIPath = "" // Don't overwrite OpenAPI endpoints defined in the original huma.API
loggedCfg.DocsPath = ""
loggedCfg.SchemasPath = ""

// Create a separate huma.API instance based on chi group.
loggedHumaApi := humachi.New(loggedMux, loggedCfg)

// Create a new inherited APIGen instance (that already has '/v1' prefix from `api`) with
// the dedicated huma.API (that uses chi's logger middleware)
loggedApi := api.ReplaceHumaAPI(loggedHumaApi)

hureg.Get(loggedApi, "/dog", dogHandler) // GET /v1/dog (with chi's logger middleware)
```

The trick works because both `huma.API` instances share the same OpenAPI pointer, so both `/v1/cat` and `/v1/dog`
will appear in the same OpenAPI spec that is served by the original `humaApi`.

## See also

- [OpenAPI endpoints](./openapi_endpoints.md) and multiple scoped specs
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/cardinalby/hureg
go 1.22

require (
github.com/danielgtaylor/huma/v2 v2.25.0
github.com/stretchr/testify v1.9.0
github.com/danielgtaylor/huma/v2 v2.26.0
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
13 changes: 5 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
github.com/danielgtaylor/huma/v2 v2.18.0 h1:L6AoiCD9WGxUFnAQMZpEub1hnRJpEs7ZUdWwvkrUWHE=
github.com/danielgtaylor/huma/v2 v2.18.0/go.mod h1:fFOnahr3rZdFha4rqDq7rjb8q3CPuZvCjoP37qg8fTI=
github.com/danielgtaylor/huma/v2 v2.25.0 h1:8q/tZLozDs2oFPUHS1xaFVa1mlNYBXV8UbmSQUQeAXo=
github.com/danielgtaylor/huma/v2 v2.25.0/go.mod h1:NbSFXRoOMh3BVmiLJQ9EbUpnPas7D9BeOxF/pZBAGa0=
github.com/danielgtaylor/huma/v2 v2.26.0 h1:lON4pIcckuSQJNDi6WkOu0sS7mxvlNkTAGbc3BrRXTc=
github.com/danielgtaylor/huma/v2 v2.26.0/go.mod h1:NbSFXRoOMh3BVmiLJQ9EbUpnPas7D9BeOxF/pZBAGa0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down

0 comments on commit 8dbc548

Please sign in to comment.