1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package spec
16
17 import (
18 "encoding/json"
19 "fmt"
20 "log"
21 "net/url"
22 "reflect"
23 "strings"
24
25 "github.com/go-openapi/swag"
26 )
27
28
29
30
31
32
33
34
35
36 var PathLoader = func(pth string) (json.RawMessage, error) {
37 data, err := swag.LoadFromFileOrHTTP(pth)
38 if err != nil {
39 return nil, err
40 }
41 return json.RawMessage(data), nil
42 }
43
44
45
46 type resolverContext struct {
47
48
49
50
51 circulars map[string]bool
52 basePath string
53 loadDoc func(string) (json.RawMessage, error)
54 rootID string
55 }
56
57 func newResolverContext(options *ExpandOptions) *resolverContext {
58 expandOptions := optionsOrDefault(options)
59
60
61 var loader func(string) (json.RawMessage, error)
62 if expandOptions.PathLoader == nil {
63 loader = PathLoader
64 } else {
65 loader = expandOptions.PathLoader
66 }
67
68 return &resolverContext{
69 circulars: make(map[string]bool),
70 basePath: expandOptions.RelativeBase,
71 loadDoc: loader,
72 }
73 }
74
75 type schemaLoader struct {
76 root interface{}
77 options *ExpandOptions
78 cache ResolutionCache
79 context *resolverContext
80 }
81
82 func (r *schemaLoader) transitiveResolver(basePath string, ref Ref) *schemaLoader {
83 if ref.IsRoot() || ref.HasFragmentOnly {
84 return r
85 }
86
87 baseRef := MustCreateRef(basePath)
88 currentRef := normalizeRef(&ref, basePath)
89 if strings.HasPrefix(currentRef.String(), baseRef.String()) {
90 return r
91 }
92
93
94 rootURL := currentRef.GetURL()
95 rootURL.Fragment = ""
96 root, _ := r.cache.Get(rootURL.String())
97
98
99
100 newOptions := r.options
101 newOptions.RelativeBase = rootURL.String()
102
103 return defaultSchemaLoader(root, newOptions, r.cache, r.context)
104 }
105
106 func (r *schemaLoader) updateBasePath(transitive *schemaLoader, basePath string) string {
107 if transitive != r {
108 if transitive.options != nil && transitive.options.RelativeBase != "" {
109 return normalizeBase(transitive.options.RelativeBase)
110 }
111 }
112
113 return basePath
114 }
115
116 func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error {
117 tgt := reflect.ValueOf(target)
118 if tgt.Kind() != reflect.Ptr {
119 return ErrResolveRefNeedsAPointer
120 }
121
122 if ref.GetURL() == nil {
123 return nil
124 }
125
126 var (
127 res interface{}
128 data interface{}
129 err error
130 )
131
132
133
134 root := r.root
135 if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" {
136 if baseRef, erb := NewRef(basePath); erb == nil {
137 root, _, _, _ = r.load(baseRef.GetURL())
138 }
139 }
140
141 if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil {
142 data = root
143 } else {
144 baseRef := normalizeRef(ref, basePath)
145 data, _, _, err = r.load(baseRef.GetURL())
146 if err != nil {
147 return err
148 }
149 }
150
151 res = data
152 if ref.String() != "" {
153 res, _, err = ref.GetPointer().Get(data)
154 if err != nil {
155 return err
156 }
157 }
158 return swag.DynamicJSONToStruct(res, target)
159 }
160
161 func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
162 debugLog("loading schema from url: %s", refURL)
163 toFetch := *refURL
164 toFetch.Fragment = ""
165
166 var err error
167 pth := toFetch.String()
168 normalized := normalizeBase(pth)
169 debugLog("loading doc from: %s", normalized)
170
171 data, fromCache := r.cache.Get(normalized)
172 if fromCache {
173 return data, toFetch, fromCache, nil
174 }
175
176 b, err := r.context.loadDoc(normalized)
177 if err != nil {
178 return nil, url.URL{}, false, err
179 }
180
181 var doc interface{}
182 if err := json.Unmarshal(b, &doc); err != nil {
183 return nil, url.URL{}, false, err
184 }
185 r.cache.Set(normalized, doc)
186
187 return doc, toFetch, fromCache, nil
188 }
189
190
191
192
193 func (r *schemaLoader) isCircular(ref *Ref, basePath string, parentRefs ...string) (foundCycle bool) {
194 normalizedRef := normalizeURI(ref.String(), basePath)
195 if _, ok := r.context.circulars[normalizedRef]; ok {
196
197 foundCycle = true
198 return
199 }
200 foundCycle = swag.ContainsStrings(parentRefs, normalizedRef)
201 if foundCycle {
202 r.context.circulars[normalizedRef] = true
203 }
204 return
205 }
206
207
208
209
210
211
212
213
214 func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error {
215 return r.resolveRef(ref, target, basePath)
216 }
217
218 func (r *schemaLoader) deref(input interface{}, parentRefs []string, basePath string) error {
219 var ref *Ref
220 switch refable := input.(type) {
221 case *Schema:
222 ref = &refable.Ref
223 case *Parameter:
224 ref = &refable.Ref
225 case *Response:
226 ref = &refable.Ref
227 case *PathItem:
228 ref = &refable.Ref
229 default:
230 return fmt.Errorf("unsupported type: %T: %w", input, ErrDerefUnsupportedType)
231 }
232
233 curRef := ref.String()
234 if curRef == "" {
235 return nil
236 }
237
238 normalizedRef := normalizeRef(ref, basePath)
239 normalizedBasePath := normalizedRef.RemoteURI()
240
241 if r.isCircular(normalizedRef, basePath, parentRefs...) {
242 return nil
243 }
244
245 if err := r.resolveRef(ref, input, basePath); r.shouldStopOnError(err) {
246 return err
247 }
248
249 if ref.String() == "" || ref.String() == curRef {
250
251 return nil
252 }
253
254 parentRefs = append(parentRefs, normalizedRef.String())
255 return r.deref(input, parentRefs, normalizedBasePath)
256 }
257
258 func (r *schemaLoader) shouldStopOnError(err error) bool {
259 if err != nil && !r.options.ContinueOnError {
260 return true
261 }
262
263 if err != nil {
264 log.Println(err)
265 }
266
267 return false
268 }
269
270 func (r *schemaLoader) setSchemaID(target interface{}, id, basePath string) (string, string) {
271 debugLog("schema has ID: %s", id)
272
273
274
275 var refPath string
276 if strings.HasSuffix(id, "/") {
277
278 refPath = fmt.Sprintf("%s%s", id, "placeholder.json")
279 } else {
280 refPath = id
281 }
282
283
284
285
286 newBasePath := normalizeURI(refPath, basePath)
287
288
289 r.cache.Set(newBasePath, target)
290
291
292
293 if basePath == r.context.basePath {
294 debugLog("root document is a schema with ID: %s (normalized as:%s)", id, newBasePath)
295 r.context.rootID = newBasePath
296 }
297
298 return newBasePath, refPath
299 }
300
301 func defaultSchemaLoader(
302 root interface{},
303 expandOptions *ExpandOptions,
304 cache ResolutionCache,
305 context *resolverContext) *schemaLoader {
306
307 if expandOptions == nil {
308 expandOptions = &ExpandOptions{}
309 }
310
311 cache = cacheOrDefault(cache)
312
313 if expandOptions.RelativeBase == "" {
314
315
316
317 expandOptions.RelativeBase = baseForRoot(root, cache)
318 }
319 debugLog("effective expander options: %#v", expandOptions)
320
321 if context == nil {
322 context = newResolverContext(expandOptions)
323 }
324
325 return &schemaLoader{
326 root: root,
327 options: expandOptions,
328 cache: cache,
329 context: context,
330 }
331 }
332
View as plain text