huma
huma copied to clipboard
[feature]: Add ability to assign routes to variables
It would be good to have the ability to assign routes to variables... Something like:
huma.Get(api, "/confirm/{token}", userapi.ConfirmEmailHandler).AssignTo(&route.ConfirmEmail)
// and use it like
emailConfirmationLink := baseUrl + route.ConfirmEmail(user.EmailConfirmationToken)
SendEmailConfirmation(user.Name, emailConfirmationLink)
Even more general way:
huma.Get(api, "/confirm/{token}", userapi.ConfirmEmailHandler).AssignTo(&route.ConfirmEmail)
// and use it like
emailConfirmationLink := baseUrl + route.ConfirmEmail.Path(user.EmailConfirmationToken)
SendEmailConfirmation(user.Name, emailConfirmationLink)
// or/and
emailConfirmationLink := route.ConfirmEmail.Url(baseUrl, user.EmailConfirmationToken)
SendEmailConfirmation(user.Name, emailConfirmationLink)
@bioform is this feature request mostly about rendering URLs? It seems like you could probably use a third-party URL template rendering library for this, something like:
const ConfirmEmail = "/confirm/{token}"
huma.Get(api, ConfirmEmail, userapi.ConfirmEmailHandler)
emailConfirmationLink := urltemplate.Render(ConfirmEmail, map[string]string{"token": user.EmailConfirmationToken})
Is there a reason you need this in the core Huma library instead? If you don't want an additional dependency you could write a quick/simple string replace implementation.
func Render(template string, params map[string]string) string {
for k, v := range params {
template = strings.ReplaceAll(template, "{" + k + "}", v)
}
return template
}
Thank you for the suggestion! The approach you outlined is valid and works well for simple use cases. However, I’m thinking of this feature more as syntactic sugar that could improve clarity and reduce boilerplate, especially when dealing with a larger set of routes.
For example, with the proposed approach:
const ConfirmEmail = "/confirm/{token}"
huma.Get(api, ConfirmEmail, userapi.ConfirmEmailHandler)
While functional, it may start to feel cluttered or disconnected as the number of routes grows. A typical setup might look like this:
routes := &Routes{
ConfirmEmail: "/confirm/{token}",
SomeAnotherRoute: "/another/{id}",
// ...
}
huma.Get(api, routes.ConfirmEmail, userapi.ConfirmEmailHandler)
huma.Get(api, routes.SomeAnotherRoute, userapi.SomeAnotherHandler)
// ...
In such cases, the route paths are managed separately from their related handlers, which can make the code harder to read and navigate, particularly for larger APIs.
Embedding the ability to assign routes directly to variables as part of the Huma library would centralize route definitions and their usage. For example:
huma.Get(api, "/confirm/{token}", userapi.ConfirmEmailHandler).AssignTo(&routes.ConfirmEmail)
// Later usage
emailConfirmationLink := routes.ConfirmEmail.Url(baseUrl, user.EmailConfirmationToken)
This approach keeps routes and their handlers closely coupled, improving maintainability and making the codebase more intuitive. While it’s possible to implement similar functionality with external libraries or custom code, having this natively within the Huma library would provide a more cohesive and streamlined experience for developers.
BTW, to build an absolute route URL we need to know the server URL, like here:
var config = huma.DefaultConfig("My API", "1.0.0")
func init() {
config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
"bearer": {
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
},
}
config.Servers = []*huma.Server{{URL: "/api"}}
}
Since the Huma already knows how to get this value - it can simplify things a bit.