-
Notifications
You must be signed in to change notification settings - Fork 0
Middleware
All middleware uses the core.MiddlewareFunc type:
type MiddlewareFunc func(ctx Context, next func(Context))Call next(ctx) to continue the chain. Skip it to short-circuit.
func logging(ctx core.Context, next func(core.Context)) {
start := time.Now()
next(ctx)
fmt.Printf("%s %s %d %s\n",
ctx.Method(), ctx.URL().Path, ctx.Status(), time.Since(start))
}Runs on every operation registered through the API:
api := neoma.NewAPI(config, adapter)
api.UseMiddleware(logging, auth)Attach middleware to a single operation via op.Middlewares:
neoma.Register(api, core.Operation{
Method: http.MethodPost,
Path: "/upload",
Middlewares: core.Middlewares{rateLimitMiddleware},
}, handler)Or with convenience functions:
neoma.Post[I, O](api, "/upload", handler, func(op *core.Operation) {
op.Middlewares = core.Middlewares{rateLimitMiddleware}
})Groups scope middleware, modifiers, and transformers to a subset of operations.
import "github.com/MeGaNeKoS/neoma/middleware"
// Group with a path prefix
v1 := middleware.NewGroup(api, "/v1")
v1.UseMiddleware(v1Auth)
// Nested groups inherit parent middleware
admin := v1.Group("/admin")
admin.UseMiddleware(adminOnly)
// Only operations registered through this group get v1Auth + adminOnly
neoma.Get[I, O](admin, "/users", listAdminUsers)Full control over whether an operation proceeds to registration:
grp.UseModifier(func(op *core.Operation, next func(*core.Operation)) {
if op.Hidden {
return // skip registration
}
next(op)
})Modify the operation and always continue:
grp.UseSimpleModifier(func(op *core.Operation) {
op.Tags = append(op.Tags, "v1")
})Scope response transformers to a group:
grp.UseTransformer(func(ctx core.Context, status string, v any) (any, error) {
return map[string]any{"data": v}, nil
})Register the same operation under multiple path prefixes:
grp := middleware.NewGroup(api)
grp.UseModifier(middleware.PrefixModifier([]string{"/v1", "/v2"}))
// Registers GET /v1/items AND GET /v2/items
neoma.Get[I, O](grp, "/items", handler)middleware.Builder creates middleware customized per-operation at registration time. This is useful when the middleware behavior depends on operation metadata.
type Builder interface {
Build(op *core.Operation) core.MiddlewareFunc
}middleware.BuilderFunc is a function adapter for Builder:
type BuilderFunc func(op *core.Operation) core.MiddlewareFuncExample implementation:
type rateLimiter struct{ defaultRPS int }
func (r *rateLimiter) Build(op *core.Operation) core.MiddlewareFunc {
rps := r.defaultRPS
if v, ok := op.Metadata["rateLimit"]; ok {
rps = v.(int)
}
return func(ctx core.Context, next func(core.Context)) {
// rate limit at `rps` RPS
next(ctx)
}
}Integrate a Builder with a group. It calls Build for each operation registered through the group and prepends the resulting middleware to op.Middlewares:
grp := middleware.NewGroup(api, "/api")
grp.UseModifier(middleware.NewBuilderModifier(&rateLimiter{defaultRPS: 100}))
neoma.Get[I, O](grp, "/expensive", handler, func(op *core.Operation) {
op.Metadata = map[string]any{"rateLimit": 10}
})Build middleware for a specific operation directly:
mw := middleware.Build(&rateLimiter{defaultRPS: 100}, op)Global middleware is prepended to the middleware stack and runs before API-level middleware:
api.UseGlobalMiddleware(func(ctx core.Context, next func(core.Context)) {
ctx.SetHeader("X-Powered-By", "Neoma")
next(ctx)
})- Global middleware (
UseGlobalMiddleware) - API-level middleware (
UseMiddleware) - Group middleware (outermost to innermost)
- Operation-level middleware (
op.Middlewares) - Handler
Each layer wraps the next; the first global middleware is the outermost function in the call chain.
The middleware package provides helpers for isolated testing.
Wrap a single middleware around a handler:
handler := middleware.TestMiddleware(myMiddleware, func(ctx core.Context) {
ctx.SetStatus(200)
})
handler(testCtx)Test multiple middleware together:
chain := core.Middlewares{auth, logging}
handler := middleware.TestChain(chain, func(ctx core.Context) {
ctx.SetStatus(200)
})
handler(testCtx)Test a Builder with a specific operation:
handler := middleware.TestBuilder(&rateLimiter{defaultRPS: 10}, op,
func(ctx core.Context) {
ctx.SetStatus(200)
},
)
handler(testCtx)