Skip to content

Commit ada5c65

Browse files
committed
fixing example filtering logic
this one is persistent!
1 parent 6916b7e commit ada5c65

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

index/extract_refs.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,20 @@ func isOpenAPIExampleKeywordSegment(seenPath []string, idx int) bool {
5858
}
5959
}
6060

61-
// underOpenAPIExamplePayloadPath reports whether seenPath points to raw example payload content,
62-
// not the Example Object itself. This lets the walker keep traversing legitimate Example Objects
63-
// for $ref values while still skipping sample data under example/value/dataValue.
61+
// underOpenAPIExamplePayloadPath reports whether seenPath points to raw example payload content.
62+
// A bare `example` path is not payload by itself because libopenapi still supports a direct
63+
// `$ref` wrapper there for bundling. Once traversal moves below `example`, or into an Example
64+
// Object's `value`/`dataValue`, the path is payload and nested `$ref` keys should be ignored.
6465
func underOpenAPIExamplePayloadPath(seenPath []string) bool {
6566
for i := range seenPath {
6667
if !isOpenAPIExampleKeywordSegment(seenPath, i) {
6768
continue
6869
}
6970
switch seenPath[i] {
7071
case "example":
71-
return true
72+
if len(seenPath) > i+1 {
73+
return true
74+
}
7275
case "examples":
7376
if len(seenPath) > i+2 {
7477
switch seenPath[i+2] {
@@ -81,6 +84,18 @@ func underOpenAPIExamplePayloadPath(seenPath []string) bool {
8184
return false
8285
}
8386

87+
// isDirectOpenAPIExampleValuePath reports whether seenPath points at the value of an OpenAPI
88+
// `example` field itself. This is used to allow a top-level `$ref` wrapper while still skipping
89+
// traversal into arbitrary example payload objects.
90+
func isDirectOpenAPIExampleValuePath(seenPath []string) bool {
91+
for i := range seenPath {
92+
if seenPath[i] == "example" && isOpenAPIExampleKeywordSegment(seenPath, i) && len(seenPath) == i+1 {
93+
return true
94+
}
95+
}
96+
return false
97+
}
98+
8499
// ExtractRefs will return a deduplicated slice of references for every unique ref found in the document.
85100
// The total number of refs, will generally be much higher, you can extract those from GetRawReferenceCount()
86101
func (index *SpecIndex) ExtractRefs(ctx context.Context, node, parent *yaml.Node, seenPath []string, level int, poly bool, pName string) []*Reference {

index/extract_refs_test.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ func TestUnderOpenAPIExamplePayloadPath(t *testing.T) {
744744
want bool
745745
}{
746746
{"empty", nil, false},
747-
{"example_payload", []string{"paths", "get", "responses", "200", "content", "application/json", "schema", "example"}, true},
747+
{"example_root", []string{"paths", "get", "responses", "200", "content", "application/json", "schema", "example"}, false},
748748
{"nested_under_example_payload", []string{"components", "schemas", "Foo", "example", "nested"}, true},
749749
{"examples_collection", []string{"components", "examples"}, false},
750750
{"example_object_entry", []string{"components", "examples", "ReusableExample"}, false},
@@ -753,7 +753,7 @@ func TestUnderOpenAPIExamplePayloadPath(t *testing.T) {
753753
{"examples_data_value_payload", []string{"components", "examples", "sample", "dataValue"}, true},
754754
{"property_named_example", []string{"components", "schemas", "Foo", "properties", "example"}, false},
755755
{"property_named_examples_value", []string{"components", "schemas", "Foo", "properties", "examples", "value"}, false},
756-
{"real_example_after_property_example", []string{"components", "schemas", "Foo", "properties", "example", "example"}, true},
756+
{"real_example_after_property_example", []string{"components", "schemas", "Foo", "properties", "example", "example"}, false},
757757
}
758758
for _, tt := range tests {
759759
t.Run(tt.name, func(t *testing.T) {
@@ -1070,3 +1070,41 @@ components:
10701070
assert.False(t, rawRefs["#/components/schemas/ShouldNotIndex"])
10711071
assert.False(t, rawRefs["#/components/schemas/ShouldNotIndexData"])
10721072
}
1073+
1074+
func TestSpecIndex_ExtractRefs_SchemaExampleRefIndexedButNestedPayloadRefsIgnored(t *testing.T) {
1075+
spec := `openapi: 3.2.0
1076+
info:
1077+
title: Schema example refs
1078+
version: 1.0.0
1079+
components:
1080+
schemas:
1081+
UsesExampleRef:
1082+
type: object
1083+
example:
1084+
$ref: '#/components/examples/ReusableExample'
1085+
InlineExamplePayload:
1086+
type: object
1087+
example:
1088+
nested:
1089+
$ref: '#/components/schemas/ShouldNotIndex'
1090+
ShouldNotIndex:
1091+
type: object
1092+
examples:
1093+
ReusableExample:
1094+
value:
1095+
ok: true
1096+
`
1097+
1098+
var rootNode yaml.Node
1099+
_ = yaml.Unmarshal([]byte(spec), &rootNode)
1100+
1101+
idx := NewSpecIndexWithConfig(&rootNode, CreateOpenAPIIndexConfig())
1102+
1103+
rawRefs := make(map[string]bool)
1104+
for _, ref := range idx.GetAllReferences() {
1105+
rawRefs[ref.RawRef] = true
1106+
}
1107+
1108+
assert.True(t, rawRefs["#/components/examples/ReusableExample"])
1109+
assert.False(t, rawRefs["#/components/schemas/ShouldNotIndex"])
1110+
}

index/extract_refs_walk.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ func (index *SpecIndex) walkChildExtractRefs(node, parent *yaml.Node, state *ext
9090
if underOpenAPIExamplePayloadPath(state.seenPath) {
9191
return nil
9292
}
93+
if isDirectOpenAPIExampleValuePath(state.seenPath) && !isDirectOpenAPIExampleRefNode(node) {
94+
return nil
95+
}
9396
state.level++
9497
if isPoly, _ := index.checkPolymorphicNode(state.prev); isPoly {
9598
state.poly = true
@@ -100,6 +103,10 @@ func (index *SpecIndex) walkChildExtractRefs(node, parent *yaml.Node, state *ext
100103
return index.ExtractRefs(state.ctx, node, parent, state.seenPath, state.level, state.poly, state.polyName)
101104
}
102105

106+
func isDirectOpenAPIExampleRefNode(node *yaml.Node) bool {
107+
return utils.IsNodeMap(node) && utils.GetRefValueNode(node) != nil && len(node.Content) == 2
108+
}
109+
103110
func (index *SpecIndex) handleExtractRefsKey(
104111
node, parent *yaml.Node,
105112
state *extractRefsState,

0 commit comments

Comments
 (0)