Skip to content

Commit b1f1b31

Browse files
chore(openapi): address PR feedback - extract helper functions and add circular reachability test
1 parent 7785d22 commit b1f1b31

File tree

2 files changed

+91
-16
lines changed

2 files changed

+91
-16
lines changed

openapi/clean.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,31 @@ func trackSecuritySchemeReference(ref *ReferencedSecurityScheme, tracker map[str
313313
return nil
314314
}
315315

316+
// trackOperationTags tracks operation tag names into the tracker
317+
func trackOperationTags(op *Operation, tracker *referencedComponentTracker) error {
318+
if op == nil || tracker == nil {
319+
return nil
320+
}
321+
for _, tag := range op.GetTags() {
322+
if tracker.tags == nil {
323+
tracker.tags = make(map[string]bool)
324+
}
325+
tracker.tags[tag] = true
326+
}
327+
return nil
328+
}
329+
330+
// trackSecurityRequirementNames tracks security scheme names referenced by a security requirement
331+
func trackSecurityRequirementNames(req *SecurityRequirement, tracker map[string]bool) error {
332+
if req == nil || tracker == nil {
333+
return nil
334+
}
335+
for schemeName := range req.All() {
336+
tracker[schemeName] = true
337+
}
338+
return nil
339+
}
340+
316341
// extractComponentName extracts the component name from a reference string
317342
func extractComponentName(refStr, componentType string) string {
318343
prefix := "#/components/" + componentType + "/"
@@ -575,25 +600,11 @@ func walkAndTrackWithFilter(ctx context.Context, doc *OpenAPI, tracker *referenc
575600
},
576601
// Track operation tags (only under allowed locations)
577602
Operation: func(op *Operation) error {
578-
if op == nil {
579-
return nil
580-
}
581-
for _, tag := range op.GetTags() {
582-
if tracker.tags == nil {
583-
tracker.tags = make(map[string]bool)
584-
}
585-
tracker.tags[tag] = true
586-
}
587-
return nil
603+
return trackOperationTags(op, tracker)
588604
},
589605
// Track security requirements (special case for security schemes)
590606
Security: func(req *SecurityRequirement) error {
591-
if req != nil {
592-
for schemeName := range req.All() {
593-
tracker.securitySchemes[schemeName] = true
594-
}
595-
}
596-
return nil
607+
return trackSecurityRequirementNames(req, tracker.securitySchemes)
597608
},
598609
})
599610
if err != nil {

openapi/clean_paths_reachability_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,67 @@ paths: {}
218218

219219
assert.Equal(t, expected, actual, "All components should be removed when only self/component-only references exist")
220220
}
221+
222+
func TestClean_Reachability_CircularSchemas_Success(t *testing.T) {
223+
t.Parallel()
224+
ctx := t.Context()
225+
226+
const yml = `
227+
openapi: 3.1.0
228+
info:
229+
title: Test API
230+
version: 1.0.0
231+
paths:
232+
/keep:
233+
get:
234+
responses:
235+
"200":
236+
description: ok
237+
content:
238+
application/json:
239+
schema:
240+
$ref: "#/components/schemas/Cycle1"
241+
components:
242+
schemas:
243+
Cycle1:
244+
$ref: "#/components/schemas/Cycle2"
245+
Cycle2:
246+
$ref: "#/components/schemas/Cycle1"
247+
`
248+
249+
doc, validationErrs, err := openapi.Unmarshal(ctx, strings.NewReader(yml))
250+
require.NoError(t, err, "unmarshal should succeed")
251+
require.Empty(t, validationErrs, "input should be valid")
252+
253+
err = openapi.Clean(ctx, doc)
254+
require.NoError(t, err, "clean should succeed")
255+
256+
var buf bytes.Buffer
257+
err = openapi.Marshal(ctx, doc, &buf)
258+
require.NoError(t, err, "marshal should succeed")
259+
actual := buf.String()
260+
261+
const expected = `openapi: 3.1.0
262+
info:
263+
title: Test API
264+
version: 1.0.0
265+
paths:
266+
/keep:
267+
get:
268+
responses:
269+
"200":
270+
description: ok
271+
content:
272+
application/json:
273+
schema:
274+
$ref: "#/components/schemas/Cycle1"
275+
components:
276+
schemas:
277+
Cycle1:
278+
$ref: "#/components/schemas/Cycle2"
279+
Cycle2:
280+
$ref: "#/components/schemas/Cycle1"
281+
`
282+
283+
assert.Equal(t, expected, actual, "Clean should keep circularly referenced components reachable from paths without hanging")
284+
}

0 commit comments

Comments
 (0)