Skip to content

Commit 1cb0303

Browse files
authored
Internalize references (#443)
1 parent 7aa3c9e commit 1cb0303

File tree

2 files changed

+424
-0
lines changed

2 files changed

+424
-0
lines changed

openapi3/internalize_refs.go

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
package openapi3
2+
3+
import (
4+
"context"
5+
"path/filepath"
6+
"strings"
7+
)
8+
9+
type RefNameResolver func(string) string
10+
11+
// DefaultRefResolver is a default implementation of refNameResolver for the
12+
// InternalizeRefs function.
13+
//
14+
// If a reference points to an element inside a document, it returns the last
15+
// element in the reference using filepath.Base. Otherwise if the reference points
16+
// to a file, it returns the file name trimmed of all extensions.
17+
func DefaultRefNameResolver(ref string) string {
18+
if ref == "" {
19+
return ""
20+
}
21+
split := strings.SplitN(ref, "#", 2)
22+
if len(split) == 2 {
23+
return filepath.Base(split[1])
24+
}
25+
ref = split[0]
26+
for ext := filepath.Ext(ref); len(ext) > 0; ext = filepath.Ext(ref) {
27+
ref = strings.TrimSuffix(ref, ext)
28+
}
29+
return filepath.Base(ref)
30+
}
31+
32+
func schemaNames(s Schemas) []string {
33+
out := make([]string, 0, len(s))
34+
for i := range s {
35+
out = append(out, i)
36+
}
37+
return out
38+
}
39+
40+
func parametersMapNames(s ParametersMap) []string {
41+
out := make([]string, 0, len(s))
42+
for i := range s {
43+
out = append(out, i)
44+
}
45+
return out
46+
}
47+
48+
func isExternalRef(ref string) bool {
49+
return ref != "" && !strings.HasPrefix(ref, "#/components/")
50+
}
51+
52+
func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver) {
53+
if s == nil || !isExternalRef(s.Ref) {
54+
return
55+
}
56+
57+
name := refNameResolver(s.Ref)
58+
if _, ok := doc.Components.Schemas[name]; ok {
59+
s.Ref = "#/components/schemas/" + name
60+
return
61+
}
62+
63+
if doc.Components.Schemas == nil {
64+
doc.Components.Schemas = make(Schemas)
65+
}
66+
doc.Components.Schemas[name] = s.Value.NewRef()
67+
s.Ref = "#/components/schemas/" + name
68+
}
69+
70+
func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver) {
71+
if p == nil || !isExternalRef(p.Ref) {
72+
return
73+
}
74+
name := refNameResolver(p.Ref)
75+
if _, ok := doc.Components.Parameters[name]; ok {
76+
p.Ref = "#/components/parameters/" + name
77+
return
78+
}
79+
80+
if doc.Components.Parameters == nil {
81+
doc.Components.Parameters = make(ParametersMap)
82+
}
83+
doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
84+
p.Ref = "#/components/parameters/" + name
85+
}
86+
87+
func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver) {
88+
if h == nil || !isExternalRef(h.Ref) {
89+
return
90+
}
91+
name := refNameResolver(h.Ref)
92+
if _, ok := doc.Components.Headers[name]; ok {
93+
h.Ref = "#/components/headers/" + name
94+
return
95+
}
96+
if doc.Components.Headers == nil {
97+
doc.Components.Headers = make(Headers)
98+
}
99+
doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
100+
h.Ref = "#/components/headers/" + name
101+
}
102+
103+
func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver) {
104+
if r == nil || !isExternalRef(r.Ref) {
105+
return
106+
}
107+
name := refNameResolver(r.Ref)
108+
if _, ok := doc.Components.RequestBodies[name]; ok {
109+
r.Ref = "#/components/requestBodies/" + name
110+
return
111+
}
112+
if doc.Components.RequestBodies == nil {
113+
doc.Components.RequestBodies = make(RequestBodies)
114+
}
115+
doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
116+
r.Ref = "#/components/requestBodies/" + name
117+
}
118+
119+
func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver) {
120+
if r == nil || !isExternalRef(r.Ref) {
121+
return
122+
}
123+
name := refNameResolver(r.Ref)
124+
if _, ok := doc.Components.Responses[name]; ok {
125+
r.Ref = "#/components/responses/" + name
126+
return
127+
}
128+
if doc.Components.Responses == nil {
129+
doc.Components.Responses = make(Responses)
130+
}
131+
doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
132+
r.Ref = "#/components/responses/" + name
133+
134+
}
135+
136+
func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver) {
137+
if ss == nil || !isExternalRef(ss.Ref) {
138+
return
139+
}
140+
name := refNameResolver(ss.Ref)
141+
if _, ok := doc.Components.SecuritySchemes[name]; ok {
142+
ss.Ref = "#/components/securitySchemes/" + name
143+
return
144+
}
145+
if doc.Components.SecuritySchemes == nil {
146+
doc.Components.SecuritySchemes = make(SecuritySchemes)
147+
}
148+
doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value}
149+
ss.Ref = "#/components/securitySchemes/" + name
150+
151+
}
152+
153+
func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver) {
154+
if e == nil || !isExternalRef(e.Ref) {
155+
return
156+
}
157+
name := refNameResolver(e.Ref)
158+
if _, ok := doc.Components.Examples[name]; ok {
159+
e.Ref = "#/components/examples/" + name
160+
return
161+
}
162+
if doc.Components.Examples == nil {
163+
doc.Components.Examples = make(Examples)
164+
}
165+
doc.Components.Examples[name] = &ExampleRef{Value: e.Value}
166+
e.Ref = "#/components/examples/" + name
167+
168+
}
169+
170+
func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver) {
171+
if l == nil || !isExternalRef(l.Ref) {
172+
return
173+
}
174+
name := refNameResolver(l.Ref)
175+
if _, ok := doc.Components.Links[name]; ok {
176+
l.Ref = "#/components/links/" + name
177+
return
178+
}
179+
if doc.Components.Links == nil {
180+
doc.Components.Links = make(Links)
181+
}
182+
doc.Components.Links[name] = &LinkRef{Value: l.Value}
183+
l.Ref = "#/components/links/" + name
184+
185+
}
186+
187+
func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver) {
188+
if c == nil || !isExternalRef(c.Ref) {
189+
return
190+
}
191+
name := refNameResolver(c.Ref)
192+
if _, ok := doc.Components.Callbacks[name]; ok {
193+
c.Ref = "#/components/callbacks/" + name
194+
}
195+
if doc.Components.Callbacks == nil {
196+
doc.Components.Callbacks = make(Callbacks)
197+
}
198+
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
199+
c.Ref = "#/components/callbacks/" + name
200+
}
201+
202+
func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) {
203+
if s == nil {
204+
return
205+
}
206+
207+
for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
208+
for _, s2 := range list {
209+
doc.addSchemaToSpec(s2, refNameResolver)
210+
if s2 != nil {
211+
doc.derefSchema(s2.Value, refNameResolver)
212+
}
213+
}
214+
}
215+
for _, s2 := range s.Properties {
216+
doc.addSchemaToSpec(s2, refNameResolver)
217+
if s2 != nil {
218+
doc.derefSchema(s2.Value, refNameResolver)
219+
}
220+
}
221+
for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties, s.Items} {
222+
doc.addSchemaToSpec(ref, refNameResolver)
223+
if ref != nil {
224+
doc.derefSchema(ref.Value, refNameResolver)
225+
}
226+
}
227+
}
228+
229+
func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver) {
230+
for _, h := range hs {
231+
doc.addHeaderToSpec(h, refNameResolver)
232+
doc.derefParameter(h.Value.Parameter, refNameResolver)
233+
}
234+
}
235+
236+
func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver) {
237+
for _, e := range es {
238+
doc.addExampleToSpec(e, refNameResolver)
239+
}
240+
}
241+
242+
func (doc *T) derefContent(c Content, refNameResolver RefNameResolver) {
243+
for _, mediatype := range c {
244+
doc.addSchemaToSpec(mediatype.Schema, refNameResolver)
245+
if mediatype.Schema != nil {
246+
doc.derefSchema(mediatype.Schema.Value, refNameResolver)
247+
}
248+
doc.derefExamples(mediatype.Examples, refNameResolver)
249+
for _, e := range mediatype.Encoding {
250+
doc.derefHeaders(e.Headers, refNameResolver)
251+
}
252+
}
253+
}
254+
255+
func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver) {
256+
for _, l := range ls {
257+
doc.addLinkToSpec(l, refNameResolver)
258+
}
259+
}
260+
261+
func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver) {
262+
for _, e := range es {
263+
doc.addResponseToSpec(e, refNameResolver)
264+
if e.Value != nil {
265+
doc.derefHeaders(e.Value.Headers, refNameResolver)
266+
doc.derefContent(e.Value.Content, refNameResolver)
267+
doc.derefLinks(e.Value.Links, refNameResolver)
268+
}
269+
}
270+
}
271+
272+
func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver) {
273+
doc.addSchemaToSpec(p.Schema, refNameResolver)
274+
doc.derefContent(p.Content, refNameResolver)
275+
if p.Schema != nil {
276+
doc.derefSchema(p.Schema.Value, refNameResolver)
277+
}
278+
}
279+
280+
func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver) {
281+
doc.derefContent(r.Content, refNameResolver)
282+
}
283+
284+
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver) {
285+
for _, ops := range paths {
286+
// inline full operations
287+
ops.Ref = ""
288+
289+
for _, op := range ops.Operations() {
290+
doc.addRequestBodyToSpec(op.RequestBody, refNameResolver)
291+
if op.RequestBody != nil && op.RequestBody.Value != nil {
292+
doc.derefRequestBody(*op.RequestBody.Value, refNameResolver)
293+
}
294+
for _, cb := range op.Callbacks {
295+
doc.addCallbackToSpec(cb, refNameResolver)
296+
if cb.Value != nil {
297+
doc.derefPaths(*cb.Value, refNameResolver)
298+
}
299+
}
300+
doc.derefResponses(op.Responses, refNameResolver)
301+
for _, param := range op.Parameters {
302+
doc.addParameterToSpec(param, refNameResolver)
303+
if param.Value != nil {
304+
doc.derefParameter(*param.Value, refNameResolver)
305+
}
306+
}
307+
}
308+
}
309+
}
310+
311+
// InternalizeRefs removes all references to external files from the spec and moves them
312+
// to the components section.
313+
//
314+
// refNameResolver takes in references to returns a name to store the reference under locally.
315+
// It MUST return a unique name for each reference type.
316+
// A default implementation is provided that will suffice for most use cases. See the function
317+
// documention for more details.
318+
//
319+
// Example:
320+
//
321+
// doc.InternalizeRefs(context.Background(), nil)
322+
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) {
323+
if refNameResolver == nil {
324+
refNameResolver = DefaultRefNameResolver
325+
}
326+
327+
// Handle components section
328+
names := schemaNames(doc.Components.Schemas)
329+
for _, name := range names {
330+
schema := doc.Components.Schemas[name]
331+
doc.addSchemaToSpec(schema, refNameResolver)
332+
if schema != nil {
333+
schema.Ref = "" // always dereference the top level
334+
doc.derefSchema(schema.Value, refNameResolver)
335+
}
336+
}
337+
names = parametersMapNames(doc.Components.Parameters)
338+
for _, name := range names {
339+
p := doc.Components.Parameters[name]
340+
doc.addParameterToSpec(p, refNameResolver)
341+
if p != nil && p.Value != nil {
342+
p.Ref = "" // always dereference the top level
343+
doc.derefParameter(*p.Value, refNameResolver)
344+
}
345+
}
346+
doc.derefHeaders(doc.Components.Headers, refNameResolver)
347+
for _, req := range doc.Components.RequestBodies {
348+
doc.addRequestBodyToSpec(req, refNameResolver)
349+
if req != nil && req.Value != nil {
350+
req.Ref = "" // always dereference the top level
351+
doc.derefRequestBody(*req.Value, refNameResolver)
352+
}
353+
}
354+
doc.derefResponses(doc.Components.Responses, refNameResolver)
355+
for _, ss := range doc.Components.SecuritySchemes {
356+
doc.addSecuritySchemeToSpec(ss, refNameResolver)
357+
}
358+
doc.derefExamples(doc.Components.Examples, refNameResolver)
359+
doc.derefLinks(doc.Components.Links, refNameResolver)
360+
for _, cb := range doc.Components.Callbacks {
361+
doc.addCallbackToSpec(cb, refNameResolver)
362+
if cb != nil && cb.Value != nil {
363+
cb.Ref = "" // always dereference the top level
364+
doc.derefPaths(*cb.Value, refNameResolver)
365+
}
366+
}
367+
368+
doc.derefPaths(doc.Paths, refNameResolver)
369+
}

0 commit comments

Comments
 (0)