From 08cb1385d379a6245854e0a77101e19b28c5b01a Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 07:44:55 +0000 Subject: [PATCH] fix(api): redirect swagger base path Co-Authored-By: Virgil --- swagger.go | 4 +++ swagger_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/swagger.go b/swagger.go index 7ad962a..23b85a8 100644 --- a/swagger.go +++ b/swagger.go @@ -4,6 +4,7 @@ package api import ( "fmt" + "net/http" "strings" "sync" "sync/atomic" @@ -57,6 +58,9 @@ func registerSwagger(g *gin.Engine, e *Engine, groups []RouteGroup) { spec := newSwaggerSpec(e.OpenAPISpecBuilder(), groups) name := fmt.Sprintf("swagger_%d", swaggerSeq.Add(1)) swag.Register(name, spec) + g.GET(swaggerPath, func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, swaggerPath+"/") + }) g.GET(swaggerPath+"/*any", ginSwagger.WrapHandler(swaggerFiles.NewHandler(), ginSwagger.InstanceName(name))) } diff --git a/swagger_test.go b/swagger_test.go index 245d4b2..77c820b 100644 --- a/swagger_test.go +++ b/swagger_test.go @@ -111,6 +111,71 @@ func TestSwaggerEndpoint_Good_CustomPath(t *testing.T) { } } +func TestSwaggerEndpoint_Good_BasePathRedirect(t *testing.T) { + gin.SetMode(gin.TestMode) + + e, err := api.New(api.WithSwagger("Test API", "A test API service", "1.0.0")) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + srv := httptest.NewServer(e.Handler()) + defer srv.Close() + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + resp, err := client.Get(srv.URL + "/swagger") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusMovedPermanently { + t.Fatalf("expected 301 redirect, got %d", resp.StatusCode) + } + if got := resp.Header.Get("Location"); got != "/swagger/" { + t.Fatalf("expected Location=/swagger/, got %q", got) + } +} + +func TestSwaggerEndpoint_Good_CustomBasePathRedirect(t *testing.T) { + gin.SetMode(gin.TestMode) + + e, err := api.New( + api.WithSwagger("Test API", "A test API service", "1.0.0"), + api.WithSwaggerPath("/docs"), + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + srv := httptest.NewServer(e.Handler()) + defer srv.Close() + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + resp, err := client.Get(srv.URL + "/docs") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusMovedPermanently { + t.Fatalf("expected 301 redirect, got %d", resp.StatusCode) + } + if got := resp.Header.Get("Location"); got != "/docs/" { + t.Fatalf("expected Location=/docs/, got %q", got) + } +} + func TestSwaggerDisabledByDefault_Good(t *testing.T) { gin.SetMode(gin.TestMode)