huma
huma copied to clipboard
Route groups break docs
I followed the docs about Route Groups & Base URLs to group my API under /v1/sports. I'm using Chi and identical structure to the example.
The relevant part of my code:
app := NewApp(client)
api := API{app: app}
// Create a new router & API.
router := chi.NewMux()
router.Route("/v1/sports", func(router chi.Router) {
config := huma.DefaultConfig("My API", "1.0.0")
config.CreateHooks = nil // Do not add $schema links.
// config.OpenAPIPath = "/v1/sports/openapi"
routes := humachi.New(router, config)
// My routes.
})
chi.Walk(router, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
fmt.Printf("[%s]: '%s'\n", method, route)
return nil
})
It generates these routes:
...
[GET]: '/v1/sports/docs'
[GET]: '/v1/sports/openapi-3.0.json'
[GET]: '/v1/sports/openapi-3.0.yaml'
[GET]: '/v1/sports/openapi.json'
[GET]: '/v1/sports/openapi.yaml'
The problem is the docs HTML at /v1/sports/docs accesses the openapi.yaml using the following path: /openapi.yaml producing:
Setting OpenAPIPath doesn't help. If I uncomment the following line:
config.OpenAPIPath = "/v1/sports/openapi"
...this is the routes it generates:
...
[GET]: '/v1/sports/docs'
[GET]: '/v1/sports/v1/sports/openapi-3.0.json'
[GET]: '/v1/sports/v1/sports/openapi-3.0.yaml'
[GET]: '/v1/sports/v1/sports/openapi.json'
[GET]: '/v1/sports/v1/sports/openapi.yaml'
So now, when I'm accessing /v1/sports/docs, the error becomes:
It's because it expects the "correct" path based on the following code in api.go:
<elements-api
apiDescriptionUrl="` + openAPIPath + `.yaml"
router="hash"
apiDescriptionUrl="` + openAPIPath + `.yaml"
@bilus yes I don't think this is supported at the moment. You can get around it by providing your own docs route, for example setting config.DocsPath = "" and then doing something like:
router.Get("/docs", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="referrer" content="same-origin" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Sports API Docs</title>
<!-- Embed elements Elements via Web Component -->
<link href="https://unpkg.com/@stoplight/[email protected]/styles.min.css" rel="stylesheet" />
<script src="https://unpkg.com/@stoplight/[email protected]/web-components.min.js"
integrity="sha256-985sDMZYbGa0LDS8jYmC4VbkVlh7DZ0TWejFv+raZII="
crossorigin="anonymous"></script>
</head>
<body style="height: 100vh;">
<elements-api
apiDescriptionUrl="/v1/sports/openapi.yaml"
router="hash"
layout="sidebar"
tryItCredentialsPolicy="same-origin"
/>
</body>
</html>`))
})
This is copied from https://github.com/danielgtaylor/huma/blob/main/api.go#L451-L489
Hi @danielgtaylor thanks for the great library. What is the recommended way to setup the route groups without breaking docs for humachi? I realized humafiber has a NewWithGroup method, but that doesn't seem to exist for humachi.
I'm doing something where I use the same Huma config for the whole API, but have separate structs which take in a prefix variable and append it to the path when registering to Huma.
baseRouter.Route("/api", func(r chi.Router) {
humaCfg := huma.DefaultConfig("Something API", "1.0.0")
humaCfg.Servers = []*huma.Server{
{
URL: "https://something.com",
Description: "Production server",
},
}
api := humachi.New(r, humaCfg)
authClient := auth.NewAuthClient(config)
authMiddleware := auth.GetAuthMiddleware(api, authClient)
// Users
userRouter := usersRg.NewRouteGroup(userService)
userRouter.RegisterRouter("/users", api, authMiddleware)
})
func (rg *RouteGroup) RegisterRouter(basePath string, api huma.API, authMiddleware common.Middleware) {
huma.Register(api, huma.Operation{
OperationID: "getUserProfileByUsername",
Method: http.MethodGet,
Path: basePath + "/{username}",
Security: auth.SecurityScheme,
}, rg.GetUserProfileByUsername)
huma.Register(api, huma.Operation{
OperationID: "updateUsername",
Method: http.MethodPut,
Path: basePath + "/current/username",
Security: auth.SecurityScheme,
Middlewares: huma.Middlewares{authMiddleware},
}, rg.UpdateUsername)
}