Skip to content

Commit

Permalink
bubbling reg middlewares for extra huma instances
Browse files Browse the repository at this point in the history
  • Loading branch information
cardinalby committed Nov 10, 2024
1 parent be8c341 commit 7709ac0
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 27 deletions.
24 changes: 17 additions & 7 deletions api_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"github.com/cardinalby/hureg/pkg/huma/op_handler"
)

type ExtraHumaApiInstance struct {
humaAPI huma.API
bubblingRegMiddlewares RegMiddlewares
}

// APIGen is a core type of the library that wraps huma.API and stores RegMiddlewares that should be
// applied to operations before registration in Huma.
// It provides a fluent API to create derived APIGen instances with own set of actions/changes to an
Expand All @@ -16,7 +21,7 @@ type APIGen struct {
regMiddlewares RegMiddlewares
bubblingRegMiddlewares RegMiddlewares
transformers []huma.Transformer
extraHumaAPIs []huma.API
extraHumaAPIs []ExtraHumaApiInstance
}

// NewAPIGen creates a new APIGen instance with the given huma.API.
Expand Down Expand Up @@ -130,23 +135,28 @@ func (a APIGen) AddMiddlewares(middlewares ...func(huma.Context, func(huma.Conte

// AddExtraHumaAPI returns a new APIGen instance with the given huma.API added to the extraHumaAPIs.
// All operations registered with this APIGen instance or derived instances will be passed to the given `api`
// additionally to the main huma.API.
func (a APIGen) AddExtraHumaAPI(api huma.API) APIGen {
a.extraHumaAPIs = append(a.extraHumaAPIs, api)
// additionally to the main huma.API. Optional bubbling RegMiddlewares will be applied to the operations before
// the registration in the `api`.
func (a APIGen) AddExtraHumaAPI(api huma.API, bubblingRegMiddlewares ...RegMiddleware) APIGen {
a.extraHumaAPIs = append(a.extraHumaAPIs, ExtraHumaApiInstance{
humaAPI: api,
bubblingRegMiddlewares: bubblingRegMiddlewares,
})
return a
}

// GetExtraHumaAPIs returns the stored extraHumaAPIs.
func (a APIGen) GetExtraHumaAPIs() []huma.API {
func (a APIGen) GetExtraHumaAPIs() []ExtraHumaApiInstance {
return a.extraHumaAPIs
}

// AddOwnOpenAPI is a shortcut function to create a new APIGen instance and a OpenAPI object instance together.
// All operations registered with this APIGen instance or derived instances will be added to the OpenAPI object.
// `bubbleRegMiddlewares` will be additionally applied to the operations before the registration in the OpenAPI object.
// It allows you to have separate OpenAPI spec that contains only operations registered with this APIGen instance
// or derived instances.
// Use it in combination with `pkg/huma/oapi_handlers` package to serve the created OpenAPI spec.
func (a APIGen) AddOwnOpenAPI(apiConfig huma.Config) (APIGen, *huma.OpenAPI) {
func (a APIGen) AddOwnOpenAPI(apiConfig huma.Config, bubblingRegMiddlewares ...RegMiddleware) (APIGen, *huma.OpenAPI) {
humaApi := humaapi.NewDummyHumaAPI(apiConfig)
return a.AddExtraHumaAPI(humaApi), humaApi.OpenAPI()
return a.AddExtraHumaAPI(humaApi, bubblingRegMiddlewares...), humaApi.OpenAPI()
}
3 changes: 1 addition & 2 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func TestHttpServer(t *testing.T) {
addr, stop := listenAndServe(t, handler)
defer stop()

_ = addr
testServerEndpoints(t, addr)
testOpenApiSpec(t, addr)

Expand Down Expand Up @@ -174,7 +173,7 @@ func getMapsKey(data any, paths ...string) any {
}

func listenAndServe(t *testing.T, handler http.Handler) (addr string, stop func()) {
addr, err := getFreePort(8080)
addr, err := getFreePort(8089)
require.NoError(t, err)

server := &http.Server{Addr: addr, Handler: handler}
Expand Down
42 changes: 24 additions & 18 deletions register.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var testHumaRegisterer func(api huma.API, operation huma.Operation, handler any)
// before registration in Huma.
type API interface {
GetHumaAPI() huma.API
GetExtraHumaAPIs() []huma.API
GetExtraHumaAPIs() []ExtraHumaApiInstance
GetRegMiddlewares() RegMiddlewares
GetBubblingRegMiddlewares() RegMiddlewares
GetTransformers() []huma.Transformer
Expand Down Expand Up @@ -157,33 +157,39 @@ func registerImpl[I, O any](
humaAPI := api.GetHumaAPI()
initOpMetadata[I, O](humaAPI, operation, isExplicit)

humaRegister := func(op huma.Operation) {
regMiddlewares := api.GetRegMiddlewares()
for _, oh := range operationHandlers {
regMiddlewares = append(regMiddlewares, NewRegMiddleware(oh))
}
bubblingRegMiddlewares := api.GetBubblingRegMiddlewares()
for i := len(bubblingRegMiddlewares) - 1; i >= 0; i-- {
regMiddlewares = append(regMiddlewares, bubblingRegMiddlewares[i])
}

regMiddlewares.Handler(func(op huma.Operation) {
// Pass transformers saved in api to the ctx key, that will be later picked up by
// humaApiWrapper.Transform method
if apiTransformers := api.GetTransformers(); len(apiTransformers) > 0 {
op.Middlewares = append(op.Middlewares, middlewares.SetCtxTransformers(apiTransformers))
}

op_handler.ApplyToOperationPtr(&op, operationHandlers...)

humaAPIs := make([]huma.API, 0, len(api.GetExtraHumaAPIs())+1)
extraHumaAPIs := api.GetExtraHumaAPIs()
for i := len(extraHumaAPIs) - 1; i >= 0; i-- {
humaAPIs = append(humaAPIs, extraHumaAPIs[i])
}
humaAPIs = append(humaAPIs, humaAPI)

for _, apiItem := range humaAPIs {
registerWithHuma := func(humaApi huma.API, op huma.Operation) {
if testHumaRegisterer == nil {
huma.Register(apiItem, op, handler)
huma.Register(humaApi, op, handler)
} else {
testHumaRegisterer(apiItem, op, handler)
testHumaRegisterer(humaApi, op, handler)
}
}
}
reversedBubblingMiddlewares := slices.Clone(api.GetBubblingRegMiddlewares())
slices.Reverse(reversedBubblingMiddlewares)
append(api.GetRegMiddlewares(), reversedBubblingMiddlewares...).Handler(humaRegister)(*operation)
registerWithHuma(humaAPI, op)

for _, extraHumaAPI := range api.GetExtraHumaAPIs() {
extraBubblingRegMiddlewares := slices.Clone(extraHumaAPI.bubblingRegMiddlewares)
slices.Reverse(extraBubblingRegMiddlewares)
extraBubblingRegMiddlewares.Handler(func(op huma.Operation) {
registerWithHuma(extraHumaAPI.humaAPI, op)
})(op)
}
})(*operation)
}

func initOpMetadata[I, O any](humaApi huma.API, op *huma.Operation, isExplicit bool) {
Expand Down

0 comments on commit 7709ac0

Please sign in to comment.