@@ -6,7 +6,11 @@ package bundler
66
77import (
88 "context"
9+ "os"
10+ "path/filepath"
11+ "strings"
912
13+ "github.com/pb33f/libopenapi/utils"
1014 "go.yaml.in/yaml/v4"
1115
1216 "github.com/pb33f/libopenapi/index"
@@ -123,3 +127,118 @@ func replaceRefNodeWithContent(refNode, content *yaml.Node) {
123127 refNode .LineComment = content .LineComment
124128 refNode .FootComment = content .FootComment
125129}
130+
131+ // rewriteExtensionRefsForComposedBundle rebases $ref values found under x-* extension
132+ // keys from their original source file location to the bundled root document.
133+ func rewriteExtensionRefsForComposedBundle (rolodex * index.Rolodex ) {
134+ if rolodex == nil {
135+ return
136+ }
137+ rootIdx := rolodex .GetRootIndex ()
138+ if rootIdx == nil {
139+ return
140+ }
141+ rewriteExtensionRefsForComposedIndex (rootIdx , rootIdx )
142+ for _ , idx := range rolodex .GetIndexes () {
143+ rewriteExtensionRefsForComposedIndex (idx , rootIdx )
144+ }
145+ }
146+
147+ func rewriteExtensionRefsForComposedIndex (sourceIdx , rootIdx * index.SpecIndex ) {
148+ if sourceIdx == nil || rootIdx == nil {
149+ return
150+ }
151+ if sourceIdx .GetSpecAbsolutePath () == rootIdx .GetSpecAbsolutePath () {
152+ return
153+ }
154+ walkAndRewriteComposedExtensionRefs (sourceIdx .GetRootNode (), sourceIdx , rootIdx , false )
155+ }
156+
157+ func walkAndRewriteComposedExtensionRefs (node * yaml.Node , sourceIdx , rootIdx * index.SpecIndex , inExtension bool ) {
158+ if node == nil {
159+ return
160+ }
161+ switch node .Kind {
162+ case yaml .DocumentNode :
163+ for _ , child := range node .Content {
164+ walkAndRewriteComposedExtensionRefs (child , sourceIdx , rootIdx , inExtension )
165+ }
166+ case yaml .SequenceNode :
167+ for _ , child := range node .Content {
168+ walkAndRewriteComposedExtensionRefs (child , sourceIdx , rootIdx , inExtension )
169+ }
170+ case yaml .MappingNode :
171+ for i := 0 ; i + 1 < len (node .Content ); i += 2 {
172+ keyNode := node .Content [i ]
173+ valueNode := node .Content [i + 1 ]
174+ if keyNode == nil {
175+ continue
176+ }
177+ childInExtension := inExtension || strings .HasPrefix (keyNode .Value , "x-" )
178+ if childInExtension && keyNode .Value == "$ref" && valueNode != nil && valueNode .Kind == yaml .ScalarNode {
179+ valueNode .Value = rebaseExtensionRefForComposed (valueNode .Value , sourceIdx , rootIdx )
180+ continue
181+ }
182+ walkAndRewriteComposedExtensionRefs (valueNode , sourceIdx , rootIdx , childInExtension )
183+ }
184+ }
185+ }
186+
187+ func rebaseExtensionRefForComposed (refValue string , sourceIdx , rootIdx * index.SpecIndex ) string {
188+ pathPart , fragment := splitRefPathAndFragment (refValue )
189+ if pathPart == "" {
190+ if fragment == "" || sourceIdx == nil || sourceIdx .GetSpecAbsolutePath () == "" || isExternalRefURI (sourceIdx .GetSpecAbsolutePath ()) {
191+ return refValue
192+ }
193+ pathPart = sourceIdx .GetSpecAbsolutePath ()
194+ }
195+ if isExternalRefURI (pathPart ) {
196+ return refValue
197+ }
198+ sourceDir := specDir (sourceIdx )
199+ rootDir := specDir (rootIdx )
200+ if sourceDir == "" || rootDir == "" {
201+ return refValue
202+ }
203+ targetPath := pathPart
204+ if ! filepath .IsAbs (targetPath ) {
205+ targetPath = utils .CheckPathOverlap (sourceDir , targetPath , string (os .PathSeparator ))
206+ }
207+ absTarget , err := filepath .Abs (targetPath )
208+ if err != nil {
209+ return refValue
210+ }
211+ relTarget , err := filepath .Rel (rootDir , absTarget )
212+ if err != nil {
213+ return filepath .ToSlash (absTarget ) + fragment
214+ }
215+ return filepath .ToSlash (relTarget ) + fragment
216+ }
217+
218+ func splitRefPathAndFragment (refValue string ) (string , string ) {
219+ pathPart , fragment , found := strings .Cut (refValue , "#" )
220+ if found {
221+ return pathPart , "#" + fragment
222+ }
223+ return refValue , ""
224+ }
225+
226+ func isExternalRefURI (refPath string ) bool {
227+ return strings .HasPrefix (refPath , "http://" ) ||
228+ strings .HasPrefix (refPath , "https://" ) ||
229+ strings .HasPrefix (refPath , "urn:" )
230+ }
231+
232+ func specDir (idx * index.SpecIndex ) string {
233+ if idx == nil {
234+ return ""
235+ }
236+ specPath := idx .GetSpecAbsolutePath ()
237+ if specPath == "" || isExternalRefURI (specPath ) {
238+ return ""
239+ }
240+ if filepath .Ext (specPath ) == "" {
241+ return specPath
242+ }
243+ return filepath .Dir (specPath )
244+ }
0 commit comments