feat(api): default enabled transport paths in specs
Treat enabled built-in transports as having their default paths when callers omit an explicit override. This keeps manual SpecBuilder usage aligned with the engine defaults and prevents Swagger, GraphQL, WebSocket, and SSE metadata from disappearing from generated documents.\n\nCo-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ef51d9b1c3
commit
ec268c8100
2 changed files with 99 additions and 9 deletions
48
openapi.go
48
openapi.go
|
|
@ -107,7 +107,7 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
delete(spec["info"].(map[string]any), "summary")
|
||||
}
|
||||
|
||||
if swaggerPath := strings.TrimSpace(sb.SwaggerPath); swaggerPath != "" {
|
||||
if swaggerPath := sb.effectiveSwaggerPath(); swaggerPath != "" {
|
||||
spec["x-swagger-ui-path"] = normaliseSwaggerPath(swaggerPath)
|
||||
}
|
||||
if sb.SwaggerEnabled {
|
||||
|
|
@ -120,13 +120,13 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
spec["x-graphql-path"] = normaliseOpenAPIPath(graphqlPath)
|
||||
spec["x-graphql-playground"] = sb.GraphQLPlayground
|
||||
}
|
||||
if wsPath := strings.TrimSpace(sb.WSPath); wsPath != "" {
|
||||
if wsPath := sb.effectiveWSPath(); wsPath != "" {
|
||||
spec["x-ws-path"] = normaliseOpenAPIPath(wsPath)
|
||||
}
|
||||
if sb.WSEnabled {
|
||||
spec["x-ws-enabled"] = true
|
||||
}
|
||||
if ssePath := strings.TrimSpace(sb.SSEPath); ssePath != "" {
|
||||
if ssePath := sb.effectiveSSEPath(); ssePath != "" {
|
||||
spec["x-sse-path"] = normaliseOpenAPIPath(ssePath)
|
||||
}
|
||||
if sb.SSEEnabled {
|
||||
|
|
@ -279,12 +279,12 @@ func (sb *SpecBuilder) buildPaths(groups []preparedRouteGroup) map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
if wsPath := strings.TrimSpace(sb.WSPath); wsPath != "" {
|
||||
if wsPath := sb.effectiveWSPath(); wsPath != "" {
|
||||
wsPath = normaliseOpenAPIPath(wsPath)
|
||||
paths[wsPath] = wsPathItem(wsPath, operationIDs)
|
||||
}
|
||||
|
||||
if ssePath := strings.TrimSpace(sb.SSEPath); ssePath != "" {
|
||||
if ssePath := sb.effectiveSSEPath(); ssePath != "" {
|
||||
ssePath = normaliseOpenAPIPath(ssePath)
|
||||
paths[ssePath] = ssePathItem(ssePath, operationIDs)
|
||||
}
|
||||
|
|
@ -800,7 +800,7 @@ func (sb *SpecBuilder) buildTags(groups []preparedRouteGroup) []map[string]any {
|
|||
seen["graphql"] = true
|
||||
}
|
||||
|
||||
if ssePath := strings.TrimSpace(sb.SSEPath); ssePath != "" && !seen["events"] {
|
||||
if ssePath := sb.effectiveSSEPath(); ssePath != "" && !seen["events"] {
|
||||
tags = append(tags, map[string]any{
|
||||
"name": "events",
|
||||
"description": "Server-Sent Events endpoints",
|
||||
|
|
@ -1849,16 +1849,46 @@ func sseResponseHeaders() map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
// effectiveGraphQLPath returns the configured GraphQL path, or the default
|
||||
// GraphQL path when playground mode is enabled without an explicit path.
|
||||
// effectiveGraphQLPath returns the configured GraphQL path or the default
|
||||
// GraphQL path when GraphQL is enabled without an explicit path.
|
||||
func (sb *SpecBuilder) effectiveGraphQLPath() string {
|
||||
graphqlPath := strings.TrimSpace(sb.GraphQLPath)
|
||||
if graphqlPath == "" && sb.GraphQLPlayground {
|
||||
if graphqlPath == "" && (sb.GraphQLEnabled || sb.GraphQLPlayground) {
|
||||
return defaultGraphQLPath
|
||||
}
|
||||
return graphqlPath
|
||||
}
|
||||
|
||||
// effectiveSwaggerPath returns the configured Swagger UI path or the default
|
||||
// path when Swagger is enabled without an explicit override.
|
||||
func (sb *SpecBuilder) effectiveSwaggerPath() string {
|
||||
swaggerPath := strings.TrimSpace(sb.SwaggerPath)
|
||||
if swaggerPath == "" && sb.SwaggerEnabled {
|
||||
return defaultSwaggerPath
|
||||
}
|
||||
return swaggerPath
|
||||
}
|
||||
|
||||
// effectiveWSPath returns the configured WebSocket path or the default path
|
||||
// when WebSockets are enabled without an explicit override.
|
||||
func (sb *SpecBuilder) effectiveWSPath() string {
|
||||
wsPath := strings.TrimSpace(sb.WSPath)
|
||||
if wsPath == "" && sb.WSEnabled {
|
||||
return defaultWSPath
|
||||
}
|
||||
return wsPath
|
||||
}
|
||||
|
||||
// effectiveSSEPath returns the configured SSE path or the default path when
|
||||
// SSE is enabled without an explicit override.
|
||||
func (sb *SpecBuilder) effectiveSSEPath() string {
|
||||
ssePath := strings.TrimSpace(sb.SSEPath)
|
||||
if ssePath == "" && sb.SSEEnabled {
|
||||
return defaultSSEPath
|
||||
}
|
||||
return ssePath
|
||||
}
|
||||
|
||||
// documentedResponseHeaders converts route-specific response header metadata
|
||||
// into OpenAPI header objects.
|
||||
func documentedResponseHeaders(headers map[string]string) map[string]any {
|
||||
|
|
|
|||
|
|
@ -584,6 +584,66 @@ func TestSpecBuilder_Good_GraphQLPlaygroundDefaultsToGraphQLTag(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_EnabledTransportsUseDefaultPaths(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
Version: "1.0.0",
|
||||
SwaggerEnabled: true,
|
||||
GraphQLEnabled: true,
|
||||
WSEnabled: true,
|
||||
SSEEnabled: true,
|
||||
}
|
||||
|
||||
data, err := sb.Build(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
t.Fatalf("invalid JSON: %v", err)
|
||||
}
|
||||
|
||||
if got := spec["x-swagger-ui-path"]; got != "/swagger" {
|
||||
t.Fatalf("expected default swagger path, got %v", got)
|
||||
}
|
||||
if got := spec["x-graphql-path"]; got != "/graphql" {
|
||||
t.Fatalf("expected default graphql path, got %v", got)
|
||||
}
|
||||
if got := spec["x-ws-path"]; got != "/ws" {
|
||||
t.Fatalf("expected default websocket path, got %v", got)
|
||||
}
|
||||
if got := spec["x-sse-path"]; got != "/events" {
|
||||
t.Fatalf("expected default sse path, got %v", got)
|
||||
}
|
||||
|
||||
paths := spec["paths"].(map[string]any)
|
||||
for _, path := range []string{"/graphql", "/ws", "/events"} {
|
||||
if _, ok := paths[path].(map[string]any); !ok {
|
||||
t.Fatalf("expected %s path in spec", path)
|
||||
}
|
||||
}
|
||||
|
||||
tags := spec["tags"].([]any)
|
||||
foundGraphQL := false
|
||||
foundEvents := false
|
||||
for _, tag := range tags {
|
||||
tm := tag.(map[string]any)
|
||||
switch tm["name"] {
|
||||
case "graphql":
|
||||
foundGraphQL = true
|
||||
case "events":
|
||||
foundEvents = true
|
||||
}
|
||||
}
|
||||
if !foundGraphQL {
|
||||
t.Fatal("expected graphql tag when GraphQL is enabled")
|
||||
}
|
||||
if !foundEvents {
|
||||
t.Fatal("expected events tag when SSE is enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_WebSocketEndpoint(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue