From 08a2d93776b2e59449c7ab1c5357f3d8d827803f Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 01:27:40 +0000 Subject: [PATCH] fix(openapi): fall back to Describe for nil iterators Co-Authored-By: Virgil --- openapi.go | 4 +++- openapi_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/openapi.go b/openapi.go index d91d97e..71c0718 100644 --- a/openapi.go +++ b/openapi.go @@ -853,7 +853,9 @@ func isDescribableRouteGroup(g RouteGroup) bool { // can avoid slice allocation. func routeDescriptions(g RouteGroup) iter.Seq[RouteDescription] { if dg, ok := g.(DescribableGroupIter); ok { - return dg.DescribeIter() + if descIter := dg.DescribeIter(); descIter != nil { + return descIter + } } if dg, ok := g.(DescribableGroup); ok { descs := dg.Describe() diff --git a/openapi_test.go b/openapi_test.go index ec816f0..3fc70fd 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -54,6 +54,20 @@ func (s *iterStubGroup) DescribeIter() iter.Seq[api.RouteDescription] { } } +type iterNilFallbackGroup struct { + name string + basePath string + descs []api.RouteDescription +} + +func (s *iterNilFallbackGroup) Name() string { return s.name } +func (s *iterNilFallbackGroup) BasePath() string { return s.basePath } +func (s *iterNilFallbackGroup) RegisterRoutes(rg *gin.RouterGroup) {} +func (s *iterNilFallbackGroup) Describe() []api.RouteDescription { return s.descs } +func (s *iterNilFallbackGroup) DescribeIter() iter.Seq[api.RouteDescription] { + return nil +} + type countingIterGroup struct { name string basePath string @@ -565,6 +579,44 @@ func TestSpecBuilder_Good_DescribeIterSnapshotOnce(t *testing.T) { } } +func TestSpecBuilder_Good_DescribeIterNilFallsBackToDescribe(t *testing.T) { + sb := &api.SpecBuilder{ + Title: "Test", + Version: "1.0.0", + } + + group := &iterNilFallbackGroup{ + name: "fallback-iter", + basePath: "/api/fallback-iter", + descs: []api.RouteDescription{ + { + Method: "GET", + Path: "/status", + Summary: "Fallback status", + Tags: []string{"fallback-iter"}, + Response: map[string]any{ + "type": "object", + }, + }, + }, + } + + data, err := sb.Build([]api.RouteGroup{group}) + 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) + } + + op := spec["paths"].(map[string]any)["/api/fallback-iter/status"].(map[string]any)["get"].(map[string]any) + if op["summary"] != "Fallback status" { + t.Fatalf("expected summary='Fallback status', got %v", op["summary"]) + } +} + func TestSpecBuilder_Good_SecuredResponses(t *testing.T) { sb := &api.SpecBuilder{ Title: "Test",