feat(api): document rate limit and timeout responses
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
726938f04a
commit
fd09309ce9
2 changed files with 103 additions and 44 deletions
133
openapi.go
133
openapi.go
|
|
@ -96,16 +96,7 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
|
|||
"description": "Returns server health status",
|
||||
"tags": []string{"system"},
|
||||
"operationId": operationID("get", "/health", operationIDs),
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "Server is healthy",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(map[string]any{"type": "string"}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": healthResponses(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -129,40 +120,7 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
|
|||
"bearerAuth": []any{},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "Successful response",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(rd.Response),
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": map[string]any{
|
||||
"description": "Bad request",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"401": map[string]any{
|
||||
"description": "Unauthorised",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"403": map[string]any{
|
||||
"description": "Forbidden",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": operationResponses(rd.Response),
|
||||
}
|
||||
|
||||
// Add request body for methods that accept one.
|
||||
|
|
@ -200,6 +158,93 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
|
|||
return paths
|
||||
}
|
||||
|
||||
// operationResponses builds the standard response set for a documented API
|
||||
// operation. The framework always exposes the common envelope responses, plus
|
||||
// middleware-driven 429 and 504 errors.
|
||||
func operationResponses(dataSchema map[string]any) map[string]any {
|
||||
return map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "Successful response",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(dataSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": map[string]any{
|
||||
"description": "Bad request",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"401": map[string]any{
|
||||
"description": "Unauthorised",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"403": map[string]any{
|
||||
"description": "Forbidden",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"429": map[string]any{
|
||||
"description": "Too many requests",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"504": map[string]any{
|
||||
"description": "Gateway timeout",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// healthResponses builds the response set for the built-in health endpoint.
|
||||
// It stays public, but rate limiting and timeouts can still apply.
|
||||
func healthResponses() map[string]any {
|
||||
return map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "Server is healthy",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(map[string]any{"type": "string"}),
|
||||
},
|
||||
},
|
||||
},
|
||||
"429": map[string]any{
|
||||
"description": "Too many requests",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
"504": map[string]any{
|
||||
"description": "Gateway timeout",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": envelopeSchema(nil),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// buildTags generates the tags array from all RouteGroups.
|
||||
func (sb *SpecBuilder) buildTags(groups []RouteGroup) []map[string]any {
|
||||
tags := []map[string]any{
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ func TestSpecBuilder_Good_EmptyGroups(t *testing.T) {
|
|||
if _, ok := paths["/health"]; !ok {
|
||||
t.Fatal("expected /health path in spec")
|
||||
}
|
||||
health := paths["/health"].(map[string]any)["get"].(map[string]any)
|
||||
healthResponses := health["responses"].(map[string]any)
|
||||
if _, ok := healthResponses["429"]; !ok {
|
||||
t.Fatal("expected 429 response on /health")
|
||||
}
|
||||
if _, ok := healthResponses["504"]; !ok {
|
||||
t.Fatal("expected 504 response on /health")
|
||||
}
|
||||
|
||||
// Verify system tag exists.
|
||||
tags := spec["tags"].([]any)
|
||||
|
|
@ -226,6 +234,12 @@ func TestSpecBuilder_Good_SecuredResponses(t *testing.T) {
|
|||
if _, ok := responses["403"]; !ok {
|
||||
t.Fatal("expected 403 response in secured operation")
|
||||
}
|
||||
if _, ok := responses["429"]; !ok {
|
||||
t.Fatal("expected 429 response in secured operation")
|
||||
}
|
||||
if _, ok := responses["504"]; !ok {
|
||||
t.Fatal("expected 504 response in secured operation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_EnvelopeWrapping(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue