Skip to content

Commit 7b6593a

Browse files
califlowerdaveshanley
authored andcommitted
Fix Bundler Compose Mode Breaking with Non Filename Dots
1 parent 763853d commit 7b6593a

File tree

2 files changed

+101
-17
lines changed

2 files changed

+101
-17
lines changed

bundler/composer_functions.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,33 +156,38 @@ func remapIndex(idx *index.SpecIndex, processedNodes *orderedmap.Map[string, *pr
156156

157157
func renameRef(idx *index.SpecIndex, def string, processedNodes *orderedmap.Map[string, *processRef]) string {
158158
if strings.Contains(def, "#/") {
159-
160159
defSplit := strings.Split(def, "#/")
161160
if len(defSplit) != 2 {
162161
return def
163162
}
164-
split := strings.Split(defSplit[1], "/")
165-
if ref := processedNodes.GetOrZero(def); ref != nil {
166-
return fmt.Sprintf("#/%s/%s", strings.Join(split[:len(split)-1], "/"), ref.name)
163+
ptr := defSplit[1] // e.g. components/schemas/Foo
164+
segs := strings.Split(ptr, "/")
165+
if len(segs) < 2 {
166+
return def
167167
}
168-
if ref, ok := idx.GetMappedReferences()[def]; ok {
169-
ext := filepath.Ext(ref.Name)
170-
name := ref.Name
171-
if ext != "" {
172-
name = strings.Replace(ref.Name, ext, "", 1)
168+
prefix := strings.Join(segs[:len(segs)-1], "/")
169+
170+
// reference already renamed during composition
171+
if pr := processedNodes.GetOrZero(def); pr != nil {
172+
return fmt.Sprintf("#/%s/%s", prefix, pr.name)
173+
}
174+
175+
if idx != nil {
176+
if ref, ok := idx.GetMappedReferences()[def]; ok && ref != nil {
177+
return fmt.Sprintf("#/%s/%s", prefix, ref.Name)
173178
}
174-
return fmt.Sprintf("#/%s/%s", strings.Join(split[:len(split)-1], "/"), name)
175179
}
180+
181+
// fallback – keep last segment
182+
return fmt.Sprintf("#/%s/%s", prefix, segs[len(segs)-1])
176183
}
177184

178-
// handle root file imports.
179-
name := ""
180-
if pn := processedNodes.GetOrZero(def); pn != nil {
181-
if len(pn.location) > 0 {
182-
name = fmt.Sprintf("#/%s", strings.Join(pn.location, "/"))
183-
}
185+
// root-file import lifted into components
186+
if pn := processedNodes.GetOrZero(def); pn != nil && len(pn.location) > 0 {
187+
return "#/" + strings.Join(pn.location, "/")
184188
}
185-
return name
189+
190+
return def
186191
}
187192

188193
func rewireRef(idx *index.SpecIndex, ref *index.Reference, fullDef string, processedNodes *orderedmap.Map[string, *processRef]) {

bundler/composer_functions_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package bundler
55

66
import (
7+
"errors"
8+
"github.com/pb33f/libopenapi/orderedmap"
79
"testing"
810

911
"github.com/pb33f/libopenapi"
@@ -95,3 +97,80 @@ func TestProcessRef_UnknownLocation_ThreeStep(t *testing.T) {
9597
assert.NoError(t, err)
9698
assert.Len(t, config.inlineRequired, 1)
9799
}
100+
101+
// ---- RenameRef Tests ----
102+
103+
// A component key that contains a dot (“asdf.zxcv”) must *not* be shortened to
104+
// “asdf” when we re-wire references.
105+
func TestRenameRef_KeepsDotInComponentName(t *testing.T) {
106+
spec := []byte(`
107+
openapi: 3.1.0
108+
info:
109+
title: Test
110+
version: 0.0.0
111+
components:
112+
schemas:
113+
"asdf.zxcv":
114+
type: object
115+
Foo:
116+
allOf:
117+
- $ref: '#/components/schemas/asdf.zxcv'
118+
`)
119+
120+
doc, err := libopenapi.NewDocument(spec)
121+
assert.NoError(t, err)
122+
123+
v3doc, errs := doc.BuildV3Model()
124+
assert.NoError(t, errors.Join(errs...))
125+
126+
idx := v3doc.Model.Index
127+
128+
processed := orderedmap.New[string, *processRef]()
129+
130+
got := renameRef(idx, "#/components/schemas/asdf.zxcv", processed)
131+
132+
assert.Equal(t,
133+
"#/components/schemas/asdf.zxcv",
134+
got,
135+
"renameRef must not strip the .zxcv part from the component key")
136+
}
137+
138+
// A reference that really *is* a filename + JSON-pointer must still have the
139+
// extension stripped
140+
func TestRenameRef_FilePointer_Extensions(t *testing.T) {
141+
exts := []string{".yaml", ".yml", ".json"}
142+
143+
for _, ext := range exts {
144+
def := "schemas/pet" + ext + "#/components/schemas/Pet"
145+
got := renameRef(nil, def, orderedmap.New[string, *processRef]())
146+
assert.Equal(t, "#/components/schemas/Pet", got,
147+
"extension %s should not affect the pointer rewrite", ext)
148+
}
149+
}
150+
151+
// If a component name has already been changed during composition,
152+
// renameRef must return that new name.
153+
func TestRenameRef_RespectsAlreadyRenamedComponent(t *testing.T) {
154+
ps := orderedmap.New[string, *processRef]()
155+
ps.Set("#/components/schemas/asdf.zxcv",
156+
&processRef{name: "asdf__1", location: []string{}})
157+
158+
got := renameRef(nil,
159+
"#/components/schemas/asdf.zxcv",
160+
ps)
161+
162+
assert.Equal(t,
163+
"#/components/schemas/asdf__1",
164+
got,
165+
"renameRef should use the name stored in processedNodes")
166+
}
167+
168+
func TestRenameRef_RootFileImport(t *testing.T) {
169+
processed := orderedmap.New[string, *processRef]()
170+
processed.Set("schemas/pet.yaml",
171+
&processRef{location: []string{"components", "schemas", "Pet"}})
172+
173+
got := renameRef(nil, "schemas/pet.yaml", processed)
174+
175+
assert.Equal(t, "#/components/schemas/Pet", got)
176+
}

0 commit comments

Comments
 (0)