Skip to content
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

feat(server/v2): Add Swagger UI support for server/v2 #23092

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions server/v2/api/swagger/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package swagger

import (
"cosmossdk.io/core/server"
)

const ServerName = "swagger"

type Config struct {
Enable bool `toml:"enable" mapstructure:"enable"`
Address string `toml:"address" mapstructure:"address"`
Path string `toml:"path" mapstructure:"path"`
}

func DefaultConfig() *Config {
return &Config{
Enable: true,
Address: "localhost:8080",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use something else than 8080 by default

Path: "/swagger/",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a path? I think keeping /swagger by default is great.

}
}

type CfgOption func(*Config)
46 changes: 46 additions & 0 deletions server/v2/api/swagger/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package swagger

import (
"net/http"
"path"
"path/filepath"
"strings"

"github.com/rakyll/statik/fs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add Required Dependency for Statik

The pipeline indicates that the module github.com/rakyll/statik/fs is missing from project dependencies. Update your go.mod by explicitly adding this dependency to avoid build failures.

Would you like assistance creating a diff to add github.com/rakyll/statik to your go.mod file?

🧰 Tools
🪛 GitHub Actions: Dependency Review

[error] 10-10: Missing required module: github.com/rakyll/statik/fs. Module needs to be added to the project dependencies.

)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing time import
You're calling time.Time{} in this file but haven't imported "time". Add the missing import to avoid compilation issues.

 import (
     "net/http"
     "path"
     "path/filepath"
     "strings"

     "github.com/rakyll/statik/fs"
+    "time"
 )

Committable suggestion skipped: line range outside the PR's diff.


// Handler returns an HTTP handler that serves the Swagger UI files
func Handler(statikFS http.FileSystem) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// If the path is empty or "/", show index.html
if r.URL.Path == "/" || r.URL.Path == "" {
r.URL.Path = "/index.html"
}

// Clearing the path
urlPath := path.Clean(r.URL.Path)

// Opening the file from statikFS
f, err := statikFS.Open(urlPath)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
defer f.Close()

// Determining the content-type
ext := strings.ToLower(filepath.Ext(urlPath))
switch ext {
case ".html":
w.Header().Set("Content-Type", "text/html")
case ".css":
w.Header().Set("Content-Type", "text/css")
case ".js":
w.Header().Set("Content-Type", "application/javascript")
case ".json":
w.Header().Set("Content-Type", "application/json")
}

http.ServeContent(w, r, urlPath, time.Time{}, f)
}
}
75 changes: 75 additions & 0 deletions server/v2/api/swagger/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package swagger

import (
"context"
"fmt"
"net/http"

"cosmossdk.io/core/server"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
serverv2 "cosmossdk.io/server/v2"
)

type Server[T transaction.Tx] struct {
logger log.Logger
config *Config
cfgOptions []CfgOption
server *http.Server
}

func New[T transaction.Tx](
logger log.Logger,
cfg server.ConfigMap,
cfgOptions ...CfgOption,
) (*Server[T], error) {
srv := &Server[T]{
logger: logger.With(log.ModuleKey, ServerName),
cfgOptions: cfgOptions,
}

serverCfg := srv.Config().(*Config)
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
if len(cfg) > 0 {
if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &serverCfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
}
srv.config = serverCfg

mux := http.NewServeMux()
mux.Handle(srv.config.Path, NewSwaggerHandler())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle the undefined NewSwaggerHandler reference.

The function NewSwaggerHandler() is not defined in the reviewed code, nor is it imported from any package. Ensure that NewSwaggerHandler, or whichever handler function you’re calling, is properly defined and imported to prevent a runtime error or undefined symbol.

Would you like assistance creating a NewSwaggerHandler() function?

🧰 Tools
🪛 golangci-lint (1.62.2)

40-40: undefined: NewSwaggerHandler

(typecheck)


srv.server = &http.Server{
Addr: srv.config.Address,
Handler: mux,
}

return srv, nil
}

func (s *Server[T]) Name() string {
return ServerName
}

func (s *Server[T]) Start(ctx context.Context) error {
if !s.config.Enable {
s.logger.Info("swagger server is disabled via config")
return nil
}

s.logger.Info("starting swagger server...", "address", s.config.Address)
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return fmt.Errorf("failed to start swagger server: %w", err)
}

return nil
}

func (s *Server[T]) Stop(ctx context.Context) error {
if !s.config.Enable {
return nil
}

s.logger.Info("stopping swagger server...", "address", s.config.Address)
return s.server.Shutdown(ctx)
}
19 changes: 19 additions & 0 deletions server/v2/server.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for tackling this. The wiring shouldn't be done in the main server, but api/swagger should be its own server component. Check out the other servers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for tackling this. The wiring shouldn't be done in the main server, but api/swagger should be its own server component. Check out the other servers.

oh you're absolutely right, gonna fix it soon!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@julienrbrt sorry for mess in commits, I had to redo some things but now everything should be fine

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import (
"path/filepath"
"strings"

"github.com/cosmos/cosmos-sdk/server/v2/api/swagger"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
"github.com/rakyll/statik/fs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for missing imports
You reference swagger and statik/fs but also rely on http.ServeMux later (lines 78, 88) without importing "net/http". Ensure all required imports are present to avoid compilation errors.

 import (
     "context"
     "errors"
     "fmt"
     "os"
     "path/filepath"
     "strings"

+    "net/http"
     "github.com/cosmos/cosmos-sdk/server/v2/api/swagger"
     "github.com/pelletier/go-toml/v2"
     "github.com/spf13/cobra"

Committable suggestion skipped: line range outside the PR's diff.

)

// ServerComponent is a server component that can be started and stopped.
Expand Down Expand Up @@ -73,6 +75,7 @@ var _ ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil)
type Server[T transaction.Tx] struct {
components []ServerComponent[T]
config ServerConfig
router *http.ServeMux
}

func NewServer[T transaction.Tx](
Expand All @@ -82,6 +85,7 @@ func NewServer[T transaction.Tx](
return &Server[T]{
config: config,
components: components,
router: http.NewServeMux(),
}
}

Expand Down Expand Up @@ -242,3 +246,18 @@ func (s *Server[T]) StartFlags() []*pflag.FlagSet {

return flags
}

func (s *Server[T]) setupSwagger() error {
cfg := s.config.API.Swagger
if !cfg.Enable {
return nil
}

statikFS, err := fs.New()
if err != nil {
return err
}

s.router.PathPrefix(cfg.Path).Handler(swagger.Handler(statikFS))
return nil
}
Loading