Open chz8494 opened 9 months ago
Thank you for raising this, this example is now fixed in latest master
.
Thank you for the quick update. I've tried it and no more errors. But it still cannot achieve what I want. Your example
apiV1.Post("/sum", sum())
s.Mount("/api/v1", apiV1)
seems providing same function as using
s.Route("/api/v1", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(sessMW, sessDoc)
r.Method(http.MethodGet, "/sum", nethttp.NewHandler(sum()))
})
})
it just adds pattern /api/v1
in front of whatever defined in apiV1
.
what I want to do is to have server version options selectable and make routes mapping correctly in swagger GUI. with your new code, if I add this line in the beginning:
r := openapi3.NewReflector()
r.Spec.WithServers(
openapi31.Server{
URL: "/api/v1",
},
openapi31.Server{
URL: "/api/v2",
}
)
s := web.NewService(r)
and if choose /api/v1
, the swagger GUI curl example will call to endpoint localhost/api/v1/api/v1/
instead of localhost/api/v1
Hi, I think you need both individual spec configuration for each versioned API and Swagger UI setup that allows selection from multiple API specs.
Please check an example https://github.com/swaggest/rest/blob/master/_examples/multi-api/main.go
// Package main implements an example where two versioned API revisions are mounted into root web service
// and are available through a service selector in Swagger UI.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/go-chi/chi/v5/middleware"
"github.com/swaggest/openapi-go"
"github.com/swaggest/openapi-go/openapi3"
"github.com/swaggest/rest/nethttp"
"github.com/swaggest/rest/web"
swg "github.com/swaggest/swgui"
swgui "github.com/swaggest/swgui/v5emb"
"github.com/swaggest/usecase"
)
func main() {
fmt.Println("Swagger UI at http://localhost:8010/api/docs.")
if err := http.ListenAndServe("localhost:8010", service()); err != nil {
log.Fatal(err)
}
}
func service() *web.Service {
// Creating root service, to host versioned APIs.
s := web.NewService(openapi3.NewReflector())
s.OpenAPISchema().SetTitle("Security and Mount Example")
// Each versioned API is exposed with its own OpenAPI schema.
v1r := openapi3.NewReflector()
v1r.SpecEns().WithServers(openapi3.Server{URL: "/api/v1/"}).WithInfo(openapi3.Info{Title: "My API of version 1"})
apiV1 := web.NewService(v1r)
v2r := openapi3.NewReflector()
v2r.SpecEns().WithServers(openapi3.Server{URL: "/api/v2/"})
apiV2 := web.NewService(v2r)
// Versioned APIs may or may not have their own middlewares and wraps.
apiV1.Wrap(
middleware.BasicAuth("Admin Access", map[string]string{"admin": "admin"}),
nethttp.HTTPBasicSecurityMiddleware(s.OpenAPICollector, "Admin", "Admin access"),
nethttp.OpenAPIAnnotationsMiddleware(s.OpenAPICollector, func(oc openapi.OperationContext) error {
oc.SetTags(append(oc.Tags(), "V1")...)
return nil
}),
)
apiV1.Post("/sum", sum())
apiV1.Post("/mul", mul())
// Once all API use cases are added, schema can be served too.
apiV1.Method(http.MethodGet, "/openapi.json", specHandler(apiV1.OpenAPICollector.SpecSchema()))
apiV2.Post("/summarization", sum())
apiV2.Post("/multiplication", mul())
apiV2.Method(http.MethodGet, "/openapi.json", specHandler(apiV2.OpenAPICollector.SpecSchema()))
// Prepared versioned API services are mounted with their base URLs into root service.
s.Mount("/api/v1", apiV1)
s.Mount("/api/v2", apiV2)
// Root docs needs a bit of hackery to expose versioned APIs as separate services.
s.Docs("/api/docs", swgui.NewWithConfig(swg.Config{
ShowTopBar: true,
SettingsUI: map[string]string{
// When "urls" are configured, Swagger UI ignores "url" and switches to multi API mode.
"urls": `[
{"url": "/api/v1/openapi.json", "name": "APIv1"},
{"url": "/api/v2/openapi.json", "name": "APIv2"}
]`,
`"urls.primaryName"`: `"APIv2"`, // Using APIv2 as default.
},
}))
// Blanket handler, for example to serve static content.
s.Mount("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("blanket handler got a request: " + r.URL.String()))
}))
return s
}
func specHandler(s openapi.SpecSchema) http.Handler {
j, err := json.Marshal(s)
if err != nil {
panic(err)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(j)
})
}
func mul() usecase.Interactor {
return usecase.NewInteractor(func(ctx context.Context, input []int, output *int) error {
*output = 1
for _, v := range input {
*output *= v
}
return nil
})
}
func sum() usecase.Interactor {
return usecase.NewInteractor(func(ctx context.Context, input []int, output *int) error {
for _, v := range input {
*output += v
}
return nil
})
}
@vearutop Thank you for the example code update, it does solved most of the problem, I can now use banner to switch json profile and gui reflects endpoint pattern correctly. but this solution seems not work with grouped route auth middleware, e.g:
s.Route("/data", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(sessMW, sessDoc)
r.Method(http.MethodGet, "/sum", nethttp.NewHandler(sum()))
})
})
the api is functioning correctly and accepting auth, but GUI doesn't have auth options/icon displayed, openapi.json
doesn't have security related content either.
I also tried with s.With(sessMW, sessDoc)
, not work either.
and the example code
apiV1.Wrap(
middleware.BasicAuth("Admin Access", map[string]string{"admin": "admin"}),
nethttp.HTTPBasicSecurityMiddleware(s.OpenAPICollector, "Admin", "Admin access"),
nethttp.OpenAPIAnnotationsMiddleware(s.OpenAPICollector, func(oc openapi.OperationContext) error {
oc.SetTags(append(oc.Tags(), "V1")...)
return nil
}),
)
causes the whole apiV1 page becomes BasicAuth
Ok, I kind of got it work by
apiV1.Route("/data", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(serviceTokenAuth, serviceTokenDoc, checkSize)
r.Method(http.MethodGet, "/sum", nethttp.NewHandler(sum()))
})
})
// Swagger GUI to have authorization schema and input
apiV1.OpenAPISchema().SetAPIKeySecurity("apiKey", "Authorization", oapi.InHeader, "API Key.")
// to add authorization schema under route group so that Swagger GUI example curl can call
for _, pi := range v1r.Spec.Paths.MapOfPathItemValues {
pi.Post.Security = []map[string][]string{
{
"apiKey": []string{},
},
}
}
apiV1.Method(http.MethodGet, "/docs/openapi.json", specHandler(apiV1.OpenAPICollector.SpecSchema()))
but it's not ideal, I'd prefer apiV1.OpenAPICollector.SpecSchema()
to pick up correct security values by itself.
Hey guys,
Very good project for swagger 3.x, really appreciated your work. I'd like to create a server with v1 and v2 server url, so was trying to follow https://github.com/swaggest/rest/blob/master/_examples/mount/main.go example to mount endpoints under
api/v1
, but has following error withservice.Mount("/api/v1", apiV1)
:I also tried to use
with this code I can see server url options in the swagger gui, but the actual endpoint logic is not correctly mapped to server selection. I'd expect to be able to call endpoint
<url>/api/v1/data
, but the server is actually only listening on<url>/data
, the swagger GUI call test does show correct curl example<url>/api/v1/data
though.