Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ BLUE := \033[0;34m
NC := \033[0m # No Color

# Tool versions
GOLANGCI_LINT_VERSION := v2.3.1
GOLANGCI_LINT_VERSION := v2.11.4
GOTESTSUM_VERSION := v1.12.3

# Normalize VERSION input so targets accept both 1.2.3 and v1.2.3.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ internalGroup := r.Group("/internal",
)
```

### Path Handling
```go
// Remove trailing slashes from all operation paths in the spec.
// "/pet/" becomes "/pet", "/" is left unchanged.
option.WithStripTrailingSlash()
```

## Advanced Features

### Rich Schema Documentation
Expand Down
2 changes: 1 addition & 1 deletion adapter/chiopenapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (r *router) Group(fn func(r Router), opts ...option.GroupOption) Router {
r.chiRouter.Group(func(chiRouter chi.Router) {
group = &router{
chiRouter: chiRouter,
specRouter: r.specRouter.Group("/", opts...),
specRouter: r.specRouter.Group("", opts...),
gen: r.gen,
}
fn(group)
Expand Down
2 changes: 2 additions & 0 deletions adapter/fiberopenapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func (r *router) Group(prefix string, handlers ...fiber.Handler) Router {
return &router{
fiberRouter: rr,
specRouter: sr,
gen: r.gen,
}
}

Expand All @@ -143,6 +144,7 @@ func (r *router) Route(prefix string, fn func(router Router), opts ...option.Gro
subRouter := &router{
fiberRouter: fr,
specRouter: sr,
gen: r.gen,
}

fn(subRouter)
Expand Down
2 changes: 2 additions & 0 deletions adapter/fiberv3openapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (r *router) Group(prefix string, handlers ...fiber.Handler) Router {
return &router{
fiberRouter: fr,
specRouter: sr,
gen: r.gen,
}
}

Expand All @@ -154,6 +155,7 @@ func (r *router) Route(prefix string, fn func(router Router), opts ...option.Gro
subRouter := &router{
fiberRouter: fr,
specRouter: sr,
gen: r.gen,
}

fn(subRouter)
Expand Down
1 change: 1 addition & 0 deletions adapter/ginopenapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func (r *router) Group(prefix string, handlers ...gin.HandlerFunc) Router {
return &router{
ginRouter: ginGroup,
specRouter: specGroup,
gen: r.gen,
}
}

Expand Down
6 changes: 4 additions & 2 deletions adapter/httprouteropenapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ func (r *router) Handler(method, path string, handler http.Handler) Route {
handler = r.middlewares[i](handler)
}
}
r.router.Handler(method, r.pathOf(path), handler)
fullPath := r.pathOf(path)
r.router.Handler(method, fullPath, handler)
rr := &route{}
if method != http.MethodConnect {
rr.specRoute = r.specRouter.Add(method, path)
rr.specRoute = r.specRouter.Add(method, fullPath)
}

return rr
Expand Down Expand Up @@ -162,6 +163,7 @@ func (r *router) Group(prefix string, middlewares ...func(http.Handler) http.Han
middlewares: append(r.middlewares, middlewares...),
specRouter: r.specRouter.Group(""),
prefix: r.pathOf(prefix),
gen: r.gen,
}
return group
}
Expand Down
2 changes: 2 additions & 0 deletions adapter/muxopenapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type route struct {
muxRoute *mux.Route
specRoute spec.Route
specRouter spec.Router
gen spec.Generator

pathPrefix string
}
Expand Down Expand Up @@ -93,6 +94,7 @@ func (r *route) Subrouter(opts ...option.GroupOption) Router {
return &router{
muxRouter: r.muxRoute.Subrouter(),
specRouter: r.specRouter.Group(r.pathPrefix, opts...),
gen: r.gen,
}
}

Expand Down
3 changes: 3 additions & 0 deletions adapter/muxopenapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func (r *router) Get(name string) Route {
return &route{
muxRoute: muxRoute,
specRouter: r.specRouter,
gen: r.gen,
}
}

Expand All @@ -76,6 +77,7 @@ func (r *router) GetRoute(name string) Route {
return &route{
muxRoute: muxRoute,
specRouter: r.specRouter,
gen: r.gen,
}
}

Expand Down Expand Up @@ -108,6 +110,7 @@ func (r *router) NewRoute() Route {
muxRoute: r.muxRouter.NewRoute(),
specRoute: r.specRouter.NewRoute(),
specRouter: r.specRouter,
gen: r.gen,
}
}

Expand Down
7 changes: 4 additions & 3 deletions adapter/muxopenapi/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ func TestRouter_Spec(t *testing.T) {
}),
),
option.WithSecurity("apiKey", option.SecurityAPIKey("api_key", openapi.SecuritySchemeAPIKeyInHeader)),
option.WithStripTrailingSlash(),
},
setup: func(r muxopenapi.Router) {
pet := r.PathPrefix("pet").Subrouter().With(
pet := r.PathPrefix("/pet").Subrouter().With(
option.GroupTags("pet"),
option.GroupSecurity("petstore_auth", "write:pets", "read:pets"),
)
Expand Down Expand Up @@ -150,7 +151,7 @@ func TestRouter_Spec(t *testing.T) {
option.Response(204, nil),
)

store := r.PathPrefix("store").Subrouter().With(
store := r.PathPrefix("/store").Subrouter().With(
option.GroupTags("store"),
)
store.HandleFunc("/order", nil).Methods("POST").With(
Expand Down Expand Up @@ -180,7 +181,7 @@ func TestRouter_Spec(t *testing.T) {
option.Response(204, nil),
)

user := r.PathPrefix("user").Subrouter().With(
user := r.PathPrefix("/user").Subrouter().With(
option.GroupTags("user"),
)
user.HandleFunc("/createWithList", nil).Methods("POST").With(
Expand Down
22 changes: 11 additions & 11 deletions adapter/muxopenapi/testdata/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ tags:
- description: Operations about user
name: user
paths:
pet/:
/pet:
get:
description: Update the details of an existing pet in the store.
operationId: updatePet
Expand Down Expand Up @@ -68,7 +68,7 @@ paths:
summary: Add a new pet
tags:
- pet
pet/{petId}:
/pet/{petId}:
get:
description: Retrieve a pet by its ID.
operationId: getPetById
Expand Down Expand Up @@ -116,7 +116,7 @@ paths:
summary: Update pet with form
tags:
- pet
pet/{petId}/uploadImage:
/pet/{petId}/uploadImage:
post:
description: Uploads an image for a pet.
operationId: uploadFile
Expand Down Expand Up @@ -150,7 +150,7 @@ paths:
summary: Upload an image for a pet
tags:
- pet
pet/delete/{petId}:
/pet/delete/{petId}:
delete:
description: Delete a pet from the store by its ID.
operationId: deletePet
Expand All @@ -174,7 +174,7 @@ paths:
summary: Delete a pet
tags:
- pet
pet/findByStatus:
/pet/findByStatus:
get:
description: Finds Pets by status. Multiple status values can be provided with
comma separated strings.
Expand Down Expand Up @@ -204,7 +204,7 @@ paths:
summary: Find pets by status
tags:
- pet
pet/findByTags:
/pet/findByTags:
get:
description: Finds Pets by tags. Multiple tags can be provided with comma separated
strings.
Expand Down Expand Up @@ -232,7 +232,7 @@ paths:
summary: Find pets by tags
tags:
- pet
store/order:
/store/order:
post:
description: Place a new order for a pet.
operationId: placeOrder
Expand All @@ -251,7 +251,7 @@ paths:
summary: Place an order
tags:
- store
store/order/{orderId}:
/store/order/{orderId}:
delete:
description: Delete an order by its ID.
operationId: deleteOrder
Expand Down Expand Up @@ -288,7 +288,7 @@ paths:
summary: Get order by ID
tags:
- store
user/:
/user:
post:
description: Create a new user in the store.
operationId: createUser
Expand All @@ -307,7 +307,7 @@ paths:
summary: Create a new user
tags:
- user
user/{username}:
/user/{username}:
delete:
description: Delete a user from the store by their username.
operationId: deleteUser
Expand Down Expand Up @@ -391,7 +391,7 @@ paths:
summary: Update an existing user
tags:
- user
user/createWithList:
/user/createWithList:
post:
description: Create multiple users in the store with a list.
operationId: createUsersWithList
Expand Down
2 changes: 1 addition & 1 deletion internal/debuglog/debuglog.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (l *Logger) LogServer(server openapi.Server) {
if len(server.Variables) > 0 {
serverInfo += ", variables: "
for name, variable := range server.Variables {
serverInfo += name + ": " + variable.Default + ", "
serverInfo += name + ": " + variable.Default + ", " //nolint:perfsprint // simple diagnostic string build
}
serverInfo = serverInfo[:len(serverInfo)-2] // Remove trailing comma and space
}
Expand Down
11 changes: 11 additions & 0 deletions internal/mapper/openapi3.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,14 @@ func OAS3OauthFlowsAuthorizationCode(flows *openapi.OAuthFlowsAuthorizationCode)
MapOfAnything: flows.MapOfAnything,
}
}

func StringMapToEncodingMap3(enc map[string]string) map[string]openapi3.Encoding {
res := map[string]openapi3.Encoding{}
for k, v := range enc {
rv := v
res[k] = openapi3.Encoding{
ContentType: &rv,
}
}
return res
}
11 changes: 11 additions & 0 deletions internal/mapper/openapi31.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,14 @@ func OAS31OauthFlowsAuthorizationCode(
MapOfAnything: flows.MapOfAnything,
}
}

func StringMapToEncodingMap31(enc map[string]string) map[string]openapi31.Encoding {
res := map[string]openapi31.Encoding{}
for k, v := range enc {
rv := v
res[k] = openapi31.Encoding{
ContentType: &rv,
}
}
return res
}
64 changes: 64 additions & 0 deletions internal/mapper/openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1118,3 +1118,67 @@ func TestOASOauth2Flows(t *testing.T) {
})
}
}

func TestStringMapToEncodingMap(t *testing.T) {
tests := []struct {
name string
input map[string]string
expected3 map[string]openapi3.Encoding
expected31 map[string]openapi31.Encoding
}{
{
name: "empty map",
input: map[string]string{},
expected3: map[string]openapi3.Encoding{},
expected31: map[string]openapi31.Encoding{},
},
{
name: "single encoding",
input: map[string]string{
"field1": "application/json",
},
expected3: map[string]openapi3.Encoding{
"field1": {
ContentType: util.PtrOf("application/json"),
},
},
expected31: map[string]openapi31.Encoding{
"field1": {
ContentType: util.PtrOf("application/json"),
},
},
},
{
name: "multiple encodings",
input: map[string]string{
"field1": "application/json",
"field2": "text/plain",
},
expected3: map[string]openapi3.Encoding{
"field1": {
ContentType: util.PtrOf("application/json"),
},
"field2": {
ContentType: util.PtrOf("text/plain"),
},
},
expected31: map[string]openapi31.Encoding{
"field1": {
ContentType: util.PtrOf("application/json"),
},
"field2": {
ContentType: util.PtrOf("text/plain"),
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result3 := mapper.StringMapToEncodingMap3(tt.input)
assert.Equal(t, tt.expected3, result3, "String map to OpenAPI 3 Encoding map conversion failed")
result31 := mapper.StringMapToEncodingMap31(tt.input)
assert.Equal(t, tt.expected31, result31, "String map to OpenAPI 3.1 Encoding map conversion failed")
})
}
}
13 changes: 7 additions & 6 deletions openapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ type Config struct {

ReflectorConfig *ReflectorConfig // Configuration for schema reflection.

DocsPath string // Path where the documentation will be served.
SpecPath string // Path for the OpenAPI specification JSON or YAML.
CacheAge *int // Cache age for OpenAPI specification responses.
DisableDocs bool // If true, disables serving OpenAPI docs.
Logger Logger // Logger for diagnostic output.
PathParser PathParser // Path parser for framework-specific path conversions.
DocsPath string // Path where the documentation will be served.
SpecPath string // Path for the OpenAPI specification JSON or YAML.
CacheAge *int // Cache age for OpenAPI specification responses.
DisableDocs bool // If true, disables serving OpenAPI docs.
StripTrailingSlash bool // If true, trailing slashes are removed from all operation paths.
Logger Logger // Logger for diagnostic output.
PathParser PathParser // Path parser for framework-specific path conversions.

UIProvider config.Provider // UI provider for the OpenAPI documentation.
SwaggerUIConfig *config.SwaggerUI // Configuration for embedded Swagger UI.
Expand Down
Loading
Loading