-
-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Best way to pass information between middlewares and then to the operation #224
Comments
I find this issue related to #197, and the proposed solution would be to use "native" Gin router to modify the request's context via I guess I was missing the idea of embedding a sharable custom struct into inputs of all operations that need it. I am OK with this approach, but in case there is a better way I'd like to hear it. Thank you for the great work! |
@servernoj there seem to be a couple of questions here. First, is it possible to set a custom value in the context in a router-agnostic middleware? Since // Temporarily rename the type to not clash with the method name.
type humaContext huma.Context
type MyContext struct {
humaContext
}
func (c *MyContext) Context() context.Context {
// Set some custom value into the context and return it.
return context.WithValue(c.humaContext.Context(), "key", "value")
}
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
// Call the next middleware in the chain with the wrapped context.
next(&MyContext{ctx})
} This is a fairly common pattern in Go so may already feel familiar. Your custom context can change/update any behavior you like to enable setting or passing whatever data you might need. Second, how best to access data from the context in an operation handler function? Your proposal of using a shared struct with a resolver as input to the operations that need access to it is exactly what I would have suggested doing. For example something like this: type UserInfo struct {
Username string
}
func (u *UserInfo) Resolve(ctx huma.Context) []error {
// Get the user info from the context.
u.Username = ctx.Context().Value("username").(string)
return nil
}
type UserResponse struct {
Body struct {
Name string `json:"name"`
}
}
func later(api huma.API) {
huma.Register(api, huma.Operation{
OperationID: "get-user",
Method: http.MethodGet,
Path: "/user",
}, func(ctx context.Context, input *struct {
UserInfo
}) (*UserResponse, error) {
resp := &UserResponse{}
resp.Body.Name = input.Username
return resp, nil
})
} Now, if you don't like the idea of using a resolver, another idea might be to have a router-specific middleware insert a new header like type UserInfo struct {
Username string `header:"InternalUsername" hidden:"true"`
} |
Thank you, @danielgtaylor. I need a bit of time to digest your answer to my first question. But the second one is all clear. I'm still [slightly] confused about where I am closing the issue as you have addressed all my questions. Thank you. |
I came from Gin, where
gin.Context.Set
andgin.Context.Get
methods were used to pass arbitrary data between middlewares up to the final handler. I understand that Huma is router agnostic, and such a thing as context value passing is not [probably] implemented because not all routers support it.I can see that middleware can pass along
huma.Context
, which in turn has a getter forcontext.Context
(and I also see that operation handler has access tocontext.Context
), but I cannot think of a way to replace it inside middleware with anothercontext.Context
generated bycontext.WithValue()
.My final goal is to use this feature to be able to parse a JWT token from the
Authorization
header in a middleware and setuserId
(or evenuser
) for all attached operations. This is a shared logic that I don't want to implement on the operation level.An alternative would be to let input structs to implement
huma.Resolver
as shown in https://huma.rocks/features/request-resolvers/#request-resolvers and dedicate a field on the input struct to carry over thatuserId
for the assigned operation... but this is not different than having that logic implemented by the operation itself (other operations cannot benefit from this).I am afraid I am thinking in the wrong terms and/or trying to move against the stream. What would be the idiomatic way of handling auth middleware that I have just described?
The text was updated successfully, but these errors were encountered: