huma
                                
                                 huma copied to clipboard
                                
                                    huma copied to clipboard
                            
                            
                            
                        global agnostic middleware
It appears that global middleware does not run without a route? If that is the case you can never terminate early.
Another scenario might be I have a middleware that looks for a specific header and does something with that before passing it down the chain regardless if the a route exists or not.
All other frameworks I have used handle this fine.
Its possible I have overlooked something in the docs?
func main() {
    api := humago.New(router, huma.DefaultConfig("My API", "1.0.0"))
    api.UseMiddleware(heartbeat.New("/ping"))
    addRoutes(api)
    ...
}
// heartbeat.go
func New(path string) func(ctx huma.Context, next func(huma.Context)) {
    return func(ctx huma.Context, next func(huma.Context)) {
        if (ctx.Method() == "GET" || ctx.Method() == "HEAD") && strings.EqualFold(ctx.URL().Path, path) {
            ctx.SetHeader("Content-Type", "text/plain")
            ctx.SetStatus(http.StatusOK)
            ctx.BodyWriter().Write([]byte("."))
            return
        }
        next(ctx)
    }
}
Yes, right now router-agnostic middlewares are only assigned at operation registration time so are specific to all the operation paths in your API. I think you're right that we should rethink this approach. I'll consider this a bug for now, thanks for reporting!
The more I look into this the more difficult it seems to be to support, due to the nature of Huma being router-agnostic and the fact that the Huma layer which creates the huma.Context comes after the router-specific middleware has run. I'm struggling to figure out a way this could work.
I think global router-agnostic middleware would require adding a middleware to the underlying router, but Go 1.22 has no middleware functionality built-in so would require wrapping the router's ServeHTTP and users would have to pass that to the server's Listen call (which isn't supported by Fiber). Not to mention if we create the huma.Context there then how does it get stored until the operation handler needs it to run? 🤔 Open to ideas on this one...
In the meantime, your heartbeat example can be accomplished this way without relying on any specific router implementation. The adapter's Handle() method bypasses registering the operation in the OpenAPI and doesn't use any of the middleware:
// Heartbeat registers a simple heartbeat endpoint with a success response.
func Heartbeat(api huma.API, path string) {
	api.Adapter().Handle(&huma.Operation{
		Method: http.MethodGet,
		Path:   path,
	}, func(ctx huma.Context) {
		ctx.SetHeader("Content-Type", "text/plain")
		ctx.SetStatus(http.StatusOK)
		ctx.BodyWriter().Write([]byte("."))
	})
}
// ...
api := humachi.New(mux, huma.DefaultConfig("Greet API", "1.0.0"))
Heartbeat(api, "/ping")
If you do want to use the middleware for this simple call, you can wrap the handler function like this:
api.Adapter().Handle(&huma.Operation{
	Method: http.MethodGet,
	Path:   path,
}, api.Middlewares().Handler(func(ctx huma.Context) {
	ctx.SetHeader("Content-Type", "text/plain")
	ctx.SetStatus(http.StatusOK)
	ctx.BodyWriter().Write([]byte("."))
}))
Sounds like a not so easy solution, appreciated that you looked into it.
The more Huma gains traction I feel that this is going to be wanted :)
Hello @mybigman ! I don’t quite understand why this functionality is needed, what global problems will this solve?
does something with that before passing it down the chain regardless if the a route exists or not.
Are we talking about custom 404 responses and etc?
For your specific task, it is easy to create two /ping endpoints with GET and HEAD operations, which will be processed by the same function.
The heartbeat probably wasn't a good choice.
As mentioned if I have a middleware that checks headers before any routes are called to do something. This wont work and would have to resort to using the routers middleware function which defeats the router agnostic approach.