Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,20 @@ func (s *Spec) AllRefs() (result []spec.Ref) {
return
}

// AllRefsByLocation returns all the references found in the document, keyed by
// where each one is declared.
//
// Keys are local JSON references into the analyzed document, with tokens
// escaped as per RFC 6901, e.g. "#/paths/~1pets/get/responses/200/schema".
//
// Unlike [Spec.AllRefs], the result is not deduplicated: the same reference
// declared in several places appears under each of its locations.
//
// The map is cloned to avoid accidental changes.
func (s *Spec) AllRefsByLocation() map[string]spec.Ref {
return cloneRefMap(s.references.allRefs)
}

// ParameterPatterns returns all the patterns found in parameters
// the map is cloned to avoid accidental changes.
func (s *Spec) ParameterPatterns() map[string]string {
Expand Down Expand Up @@ -1053,6 +1067,13 @@ func cloneStringMap(source map[string]string) map[string]string {
return res
}

func cloneRefMap(source map[string]spec.Ref) map[string]spec.Ref {
res := make(map[string]spec.Ref, len(source))
maps.Copy(res, source)

return res
}

func cloneEnumMap(source map[string][]any) map[string][]any {
res := make(map[string][]any, len(source))
maps.Copy(res, source)
Expand Down
76 changes: 76 additions & 0 deletions analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,82 @@ func TestAnalyzer_ReferenceAnalysis(t *testing.T) {
assert.Lenf(t, an.AllItemsReferences(), 3, "Expected 3 items references in this spec")
}

func TestAnalyzer_AllRefsByLocation(t *testing.T) {
t.Parallel()

doc := antest.LoadOrFail(t, filepath.Join("fixtures", "references.yml"))
an := New(doc)

byLocation := an.AllRefsByLocation()
require.NotEmpty(t, byLocation)

t.Run("should key references by where they are declared", func(t *testing.T) {
t.Parallel()

for _, fixture := range []struct {
Location string
Expected string
}{
{
Location: "#/paths/~1some~1where~1{id}/parameters/0",
Expected: "#/parameters/idParam",
},
{
Location: "#/paths/~1some~1where~1{id}/get/parameters/0",
Expected: "#/parameters/limitParam",
},
{
Location: "#/paths/~1some~1where~1{id}/get/parameters/1/items",
Expected: "#/definitions/named",
},
{
Location: "#/responses/notFound/schema",
Expected: "#/definitions/error",
},
{
Location: "#/paths/~1other~1place",
Expected: "#/x-shared-path/getItems",
},
} {
require.MapContainsT(t, byLocation, fixture.Location)
ref := byLocation[fixture.Location]
assert.EqualT(t, fixture.Expected, ref.String(),
"unexpected reference at %q", fixture.Location)
}
})

t.Run("should agree with AllRefs on the referenced values", func(t *testing.T) {
t.Parallel()

// AllRefs is the same set, deduplicated and stripped of empty references
unique := make(map[string]struct{}, len(byLocation))
for _, declared := range byLocation {
ref := declared
if ref.String() == "" {
continue
}
unique[ref.String()] = struct{}{}
}

assert.Len(t, an.AllRefs(), len(unique))
for _, found := range an.AllRefs() {
ref := found
assert.MapContainsT(t, unique, ref.String())
}
})

t.Run("should return a clone", func(t *testing.T) {
t.Parallel()

const location = "#/responses/notFound/schema"
byLocation := an.AllRefsByLocation()
delete(byLocation, location)

require.MapContainsT(t, an.AllRefsByLocation(), location,
"expected the analyzer to be unaffected by changes to the returned map")
})
}

type expectedPattern struct {
Key string
Pattern string
Expand Down
Loading