Skip to content
Open
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
1 change: 0 additions & 1 deletion datamodel/spec_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ func ExtractSpecInfoWithDocumentCheck(spec []byte, bypass bool) (*SpecInfo, erro
if err := parseJSON(spec, specInfo, &parsedSpec); err != nil {
return nil, err
}
parsed = true
specInfo.Error = errors.New("spec type not supported by libopenapi, sorry")
return specInfo, specInfo.Error
}
Expand Down
11 changes: 6 additions & 5 deletions index/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"net/url"
"path"
"path/filepath"
"slices"
"strings"
Expand Down Expand Up @@ -545,7 +546,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No
httpExp := strings.Split(ref.FullDefinition, "#/")

u, _ := url.Parse(httpExp[0])
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
abs, _ := filepath.Abs(utils.CheckPathOverlap(path.Dir(u.Path), exp[0], string(filepath.Separator)))
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(abs)
u.Fragment = ""
fullDef = fmt.Sprintf("%s#/%s", u.String(), exp[1])
Expand Down Expand Up @@ -596,8 +597,8 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No
// is the file def a http link?
if strings.HasPrefix(fileDef[0], "http") {
u, _ := url.Parse(fileDef[0])
path, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(path)
absPath, _ := filepath.Abs(utils.CheckPathOverlap(path.Dir(u.Path), exp[0], string(filepath.Separator)))
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(absPath)
fullDef = u.String()

} else {
Expand Down Expand Up @@ -830,7 +831,7 @@ func (resolver *Resolver) buildDefPath(ref *Reference, l string) string {
if strings.HasPrefix(ref.FullDefinition, "http") {

u, _ := url.Parse(ref.FullDefinition)
p, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
p, _ := filepath.Abs(utils.CheckPathOverlap(path.Dir(u.Path), exp[0], string(filepath.Separator)))
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(p)
def = fmt.Sprintf("%s#/%s", u.String(), exp[1])

Expand Down Expand Up @@ -883,7 +884,7 @@ func (resolver *Resolver) buildDefPath(ref *Reference, l string) string {

// split the url.
u, _ := url.Parse(ref.FullDefinition)
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), l, string(filepath.Separator)))
abs, _ := filepath.Abs(utils.CheckPathOverlap(path.Dir(u.Path), l, string(filepath.Separator)))
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(abs)
u.Fragment = ""
def = u.String()
Expand Down
9 changes: 9 additions & 0 deletions index/search_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
}

if index.logger != nil {
// this is a last ditch effort. if this fails, all hope is lost.
if index.GetRolodex() != nil {
for _, i := range index.GetRolodex().GetIndexes() {
v := i.FindComponent(ctx, ref)
if v != nil {
return v, v.Index, ctx
}
}
}
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
}
return nil, index, ctx
Expand Down
52 changes: 52 additions & 0 deletions index/search_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,55 @@ func TestSpecIndex_SearchIndexForReferenceWithContext(t *testing.T) {
assert.Nil(t, idx.GetRootNode())

}

// TestSearchIndexForReference_LastDitchRolodexFallback tests the last-ditch effort
// code path where a reference is found by iterating through rolodex indexes
// after all other lookup methods fail.
func TestSearchIndexForReference_LastDitchRolodexFallback(t *testing.T) {
// Primary index with NO components - searches will fail here
primarySpec := `openapi: 3.0.1
info:
title: Primary
version: "1.0"`

var primaryRoot yaml.Node
_ = yaml.Unmarshal([]byte(primarySpec), &primaryRoot)

c := CreateOpenAPIIndexConfig()
primaryIdx := NewSpecIndexWithConfig(&primaryRoot, c)

// Secondary index WITH the component we want to find
secondarySpec := `openapi: 3.0.1
info:
title: Secondary
version: "1.0"
components:
schemas:
Pet:
type: object
properties:
name:
type: string`

var secondaryRoot yaml.Node
_ = yaml.Unmarshal([]byte(secondarySpec), &secondaryRoot)

secondaryIdx := NewSpecIndexWithConfig(&secondaryRoot, c)

// Create rolodex and add secondary index
rolo := NewRolodex(c)
rolo.AddIndex(secondaryIdx)

// Set rolodex on primary index
primaryIdx.SetRolodex(rolo)

// Search for reference that:
// 1. Doesn't exist in primary index's allMappedRefs
// 2. Has roloLookup = "" (simple ref format)
// 3. Should be found via last-ditch rolodex iteration
ref, idx := primaryIdx.SearchIndexForReference("#/components/schemas/Pet")

assert.NotNil(t, ref, "Reference should be found via rolodex fallback")
assert.NotNil(t, idx, "Index should be returned")
assert.Equal(t, "Pet", ref.Name)
}
16 changes: 16 additions & 0 deletions what-changed/model/change_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ type Change struct {
// New is the new value represented as a string.
New string `json:"new,omitempty" yaml:"new,omitempty"`

// OriginalEncoded is the original value serialized to YAML (for complex types like extensions).
// Only populated for specific use cases (e.g., extension values that are objects/arrays).
OriginalEncoded string `json:"originalEncoded,omitempty" yaml:"originalEncoded,omitempty"`

// NewEncoded is the new value serialized to YAML (for complex types like extensions).
// Only populated for specific use cases (e.g., extension values that are objects/arrays).
NewEncoded string `json:"newEncoded,omitempty" yaml:"newEncoded,omitempty"`

// Breaking determines if the change is a breaking one or not.
Breaking bool `json:"breaking" yaml:"breaking"`

Expand Down Expand Up @@ -138,6 +146,14 @@ func (c *Change) MarshalJSON() ([]byte, error) {
data["new"] = c.New
}

if c.OriginalEncoded != "" {
data["originalEncoded"] = c.OriginalEncoded
}

if c.NewEncoded != "" {
data["newEncoded"] = c.NewEncoded
}

if c.Context != nil {
data["context"] = c.Context
}
Expand Down
23 changes: 23 additions & 0 deletions what-changed/model/change_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ func TestChange_MarshalJSON(t *testing.T) {
rebuilt = rinseAndRepeat(&change)
assert.Equal(t, "difficult", rebuilt["path"])

// Test OriginalEncoded field
change = Change{
OriginalEncoded: "key: value\n",
}
rebuilt = rinseAndRepeat(&change)
assert.Equal(t, "key: value\n", rebuilt["originalEncoded"])

// Test NewEncoded field
change = Change{
NewEncoded: "items:\n - one\n - two\n",
}
rebuilt = rinseAndRepeat(&change)
assert.Equal(t, "items:\n - one\n - two\n", rebuilt["newEncoded"])

// Test both encoded fields together
change = Change{
OriginalEncoded: "old: data",
NewEncoded: "new: data",
}
rebuilt = rinseAndRepeat(&change)
assert.Equal(t, "old: data", rebuilt["originalEncoded"])
assert.Equal(t, "new: data", rebuilt["newEncoded"])

prop := &PropertyChanges{Changes: []*Change{&change}}
assert.Len(t, prop.GetPropertyChanges(), 1)
}
Loading