swaggest / swgui

Embedded Swagger UI for Go
https://pkg.go.dev/github.com/swaggest/swgui
Apache License 2.0
54 stars 8 forks source link

Make swgui relocatable? #29

Closed cobber closed 4 months ago

cobber commented 2 years ago

I tried to use swaggest in my project today and I can't get the swaggest URL's to play nice with static files.

My goal was to have /api/... and /api/docs be provided by swaggest, with / being passed on to a http.FileServer, but using chi.Mount() to relocate swaggest from / to /api/ causes the swgui to 404 - but the actual REST connectors work just fine.

My code looks something like this (trimmed here for brevity):

func main() {
        // setup REST interface with OpenAPI documentation
        s := web.DefaultService()
    s.OpenAPI.Info.Title = "..."
    s.OpenAPI.Info.WithDescription("...")
    s.OpenAPI.Info.Version = "v0.0.1"
    s.Get("/item/{id}", getItemByID())
        // etc...
    s.Docs("/docs", v4emb.New)

    // static html/css/js files served from embedded FS
    uiFS := webui.StaticFS()

        // top-level router
    r := chi.NewRouter()
    r.Mount("/api/v1", s)                        // relocate swaggest to /api/v1/...
    r.Mount("/", http.FileServer(http.FS(uiFS))) // provide static files at /

        http.ListenAndServe("localhost:8080", r)
}

My problem here is that I have two different handlers (swaggest & http.FileServer) and chi only allows one to be mounted on "/", so I can't just use s.Get("/api/v1/item/{id}",...) because then I can't have the html files at "/"

If anyone has any working examples of having swaggest play nicely with other sub-routers, I'd be most interested.

by the way, I tried Mounting the FileServer on "/static", but that also didn't work. I'm thinking this may be an issue with chi :-/

Thanks in advance

vearutop commented 2 years ago

The *web.Service instance also exposes chi.Router and so you can mount static content directly into it.

Please check the following example:

package main

import (
    "context"
    "net/http"

    "github.com/swaggest/rest/web"
    "github.com/swaggest/swgui/v4emb"
    "github.com/swaggest/usecase"
)

func getItemByID() usecase.Interactor {
    type inp struct {
        ID int `path:"id"`
    }
    return usecase.NewInteractor[inp, string](func(ctx context.Context, i inp, o *string) error {
        *o = "abc!"

        return nil
    })
}

func main() {
    // setup REST interface with OpenAPI documentation
    s := web.DefaultService()
    s.OpenAPI.Info.Title = "..."
    s.OpenAPI.Info.WithDescription("...")
    s.OpenAPI.Info.Version = "v0.0.1"
    s.Get("/api/v1/item/{id}", getItemByID())
    // etc...
    s.Docs("/api/v1/docs", v4emb.New) // relocate swaggest to /api/v1/...

    // static html/css/js files served from embedded FS
    //uiFS := webui.StaticFS()

    s.Mount("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        _, _ = w.Write([]byte(r.URL.String()))
    })) // provide static files at /

    http.ListenAndServe("localhost:8080", s)
}

There is also support for Mount introspection (added recently in rest v0.2.30), please check the example: https://github.com/swaggest/rest/blob/v0.2.30/_examples/mount/main.go.

There is also a small discussion about Mount in https://github.com/swaggest/rest/issues/84.