feat: add DescribableGroup interface for OpenAPI metadata
Co-Authored-By: Virgil <virgil@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8ba1716215
commit
465bd60a8a
2 changed files with 156 additions and 0 deletions
20
group.go
20
group.go
|
|
@ -22,3 +22,23 @@ type StreamGroup interface {
|
|||
// Channels returns the list of channel names this group streams on.
|
||||
Channels() []string
|
||||
}
|
||||
|
||||
// DescribableGroup extends RouteGroup with OpenAPI metadata.
|
||||
// RouteGroups that implement this will have their endpoints
|
||||
// included in the generated OpenAPI specification.
|
||||
type DescribableGroup interface {
|
||||
RouteGroup
|
||||
// Describe returns endpoint descriptions for OpenAPI generation.
|
||||
Describe() []RouteDescription
|
||||
}
|
||||
|
||||
// RouteDescription describes a single endpoint for OpenAPI generation.
|
||||
type RouteDescription struct {
|
||||
Method string // HTTP method: GET, POST, PUT, DELETE, PATCH
|
||||
Path string // Path relative to BasePath, e.g. "/generate"
|
||||
Summary string // Short summary
|
||||
Description string // Long description
|
||||
Tags []string // OpenAPI tags for grouping
|
||||
RequestBody map[string]any // JSON Schema for request body (nil for GET)
|
||||
Response map[string]any // JSON Schema for success response data
|
||||
}
|
||||
|
|
|
|||
136
group_test.go
136
group_test.go
|
|
@ -88,3 +88,139 @@ func TestStreamGroup_Good_AlsoSatisfiesRouteGroup(t *testing.T) {
|
|||
t.Fatalf("expected Name=%q, got %q", "stub", rg.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// ── DescribableGroup interface ────────────────────────────────────────
|
||||
|
||||
// describableStub implements DescribableGroup for testing.
|
||||
type describableStub struct {
|
||||
stubGroup
|
||||
descriptions []api.RouteDescription
|
||||
}
|
||||
|
||||
func (d *describableStub) Describe() []api.RouteDescription {
|
||||
return d.descriptions
|
||||
}
|
||||
|
||||
func TestDescribableGroup_Good_ImplementsRouteGroup(t *testing.T) {
|
||||
stub := &describableStub{}
|
||||
|
||||
// Must satisfy DescribableGroup.
|
||||
var dg api.DescribableGroup = stub
|
||||
if dg.Name() != "stub" {
|
||||
t.Fatalf("expected Name=%q, got %q", "stub", dg.Name())
|
||||
}
|
||||
|
||||
// Must also satisfy RouteGroup since DescribableGroup embeds it.
|
||||
var rg api.RouteGroup = stub
|
||||
if rg.BasePath() != "/stub" {
|
||||
t.Fatalf("expected BasePath=%q, got %q", "/stub", rg.BasePath())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribableGroup_Good_DescribeReturnsRoutes(t *testing.T) {
|
||||
stub := &describableStub{
|
||||
descriptions: []api.RouteDescription{
|
||||
{
|
||||
Method: "GET",
|
||||
Path: "/items",
|
||||
Summary: "List items",
|
||||
Tags: []string{"items"},
|
||||
},
|
||||
{
|
||||
Method: "POST",
|
||||
Path: "/items",
|
||||
Summary: "Create item",
|
||||
Tags: []string{"items"},
|
||||
RequestBody: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dg api.DescribableGroup = stub
|
||||
descs := dg.Describe()
|
||||
|
||||
if len(descs) != 2 {
|
||||
t.Fatalf("expected 2 descriptions, got %d", len(descs))
|
||||
}
|
||||
if descs[0].Method != "GET" {
|
||||
t.Fatalf("expected descs[0].Method=%q, got %q", "GET", descs[0].Method)
|
||||
}
|
||||
if descs[0].Summary != "List items" {
|
||||
t.Fatalf("expected descs[0].Summary=%q, got %q", "List items", descs[0].Summary)
|
||||
}
|
||||
if descs[1].Method != "POST" {
|
||||
t.Fatalf("expected descs[1].Method=%q, got %q", "POST", descs[1].Method)
|
||||
}
|
||||
if descs[1].RequestBody == nil {
|
||||
t.Fatal("expected descs[1].RequestBody to be non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribableGroup_Good_EmptyDescribe(t *testing.T) {
|
||||
stub := &describableStub{
|
||||
descriptions: nil,
|
||||
}
|
||||
|
||||
var dg api.DescribableGroup = stub
|
||||
descs := dg.Describe()
|
||||
|
||||
if descs != nil {
|
||||
t.Fatalf("expected nil descriptions, got %v", descs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribableGroup_Good_MultipleVerbs(t *testing.T) {
|
||||
stub := &describableStub{
|
||||
descriptions: []api.RouteDescription{
|
||||
{Method: "GET", Path: "/resources", Summary: "List resources"},
|
||||
{Method: "POST", Path: "/resources", Summary: "Create resource"},
|
||||
{Method: "DELETE", Path: "/resources/:id", Summary: "Delete resource"},
|
||||
},
|
||||
}
|
||||
|
||||
var dg api.DescribableGroup = stub
|
||||
descs := dg.Describe()
|
||||
|
||||
if len(descs) != 3 {
|
||||
t.Fatalf("expected 3 descriptions, got %d", len(descs))
|
||||
}
|
||||
|
||||
expected := []string{"GET", "POST", "DELETE"}
|
||||
for i, want := range expected {
|
||||
if descs[i].Method != want {
|
||||
t.Fatalf("expected descs[%d].Method=%q, got %q", i, want, descs[i].Method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribableGroup_Bad_NilSchemas(t *testing.T) {
|
||||
stub := &describableStub{
|
||||
descriptions: []api.RouteDescription{
|
||||
{
|
||||
Method: "GET",
|
||||
Path: "/health",
|
||||
Summary: "Health check",
|
||||
RequestBody: nil,
|
||||
Response: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dg api.DescribableGroup = stub
|
||||
descs := dg.Describe()
|
||||
|
||||
if len(descs) != 1 {
|
||||
t.Fatalf("expected 1 description, got %d", len(descs))
|
||||
}
|
||||
if descs[0].RequestBody != nil {
|
||||
t.Fatalf("expected nil RequestBody, got %v", descs[0].RequestBody)
|
||||
}
|
||||
if descs[0].Response != nil {
|
||||
t.Fatalf("expected nil Response, got %v", descs[0].Response)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue