Skip to content

Commit 1490eae

Browse files
authored
openapi3: introduce (Paths).InMatchingOrder() paths iterator (#719)
1 parent de2455e commit 1490eae

File tree

8 files changed

+53
-64
lines changed

8 files changed

+53
-64
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105
- if: runner.os == 'Linux'
106106
name: Missing specification object link to definition
107107
run: |
108-
[[ 30 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
108+
[[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
109109
110110
- if: runner.os == 'Linux'
111111
name: Style around ExtensionProps embedding

openapi2/openapi2.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
4040
}
4141

4242
func (doc *T) AddOperation(path string, method string, operation *Operation) {
43-
paths := doc.Paths
44-
if paths == nil {
45-
paths = make(map[string]*PathItem)
46-
doc.Paths = paths
43+
if doc.Paths == nil {
44+
doc.Paths = make(map[string]*PathItem)
4745
}
48-
pathItem := paths[path]
46+
pathItem := doc.Paths[path]
4947
if pathItem == nil {
5048
pathItem = &PathItem{}
51-
paths[path] = pathItem
49+
doc.Paths[path] = pathItem
5250
}
5351
pathItem.SetOperation(method, operation)
5452
}

openapi2conv/openapi2_conv.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,15 +1204,13 @@ func stripNonCustomExtensions(extensions map[string]interface{}) {
12041204
}
12051205

12061206
func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) {
1207-
paths := doc2.Paths
1208-
if paths == nil {
1209-
paths = make(map[string]*openapi2.PathItem)
1210-
doc2.Paths = paths
1207+
if doc2.Paths == nil {
1208+
doc2.Paths = make(map[string]*openapi2.PathItem)
12111209
}
1212-
pathItem := paths[path]
1210+
pathItem := doc2.Paths[path]
12131211
if pathItem == nil {
12141212
pathItem = &openapi2.PathItem{}
1215-
paths[path] = pathItem
1213+
doc2.Paths[path] = pathItem
12161214
}
12171215
pathItem.ExtensionProps = extensionProps
12181216
}

openapi3/internalize_refs.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,11 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver,
193193
return false
194194
}
195195
name := refNameResolver(c.Ref)
196-
if _, ok := doc.Components.Callbacks[name]; ok {
197-
c.Ref = "#/components/callbacks/" + name
198-
}
199196
if doc.Components.Callbacks == nil {
200197
doc.Components.Callbacks = make(Callbacks)
201198
}
202-
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
203199
c.Ref = "#/components/callbacks/" + name
200+
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
204201
return true
205202
}
206203

openapi3/loader.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,7 @@ func isSingleRefElement(ref string) bool {
281281
return !strings.Contains(ref, "#")
282282
}
283283

284-
func (loader *Loader) resolveComponent(
285-
doc *T,
286-
ref string,
287-
path *url.URL,
288-
resolved interface{},
289-
) (
284+
func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved interface{}) (
290285
componentDoc *T,
291286
componentPath *url.URL,
292287
err error,
@@ -928,11 +923,10 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
928923
}
929924
id := unescapeRefString(rest)
930925

931-
definitions := doc.Components.Callbacks
932-
if definitions == nil {
926+
if doc.Components.Callbacks == nil {
933927
return failedToResolveRefFragmentPart(ref, "callbacks")
934928
}
935-
resolved := definitions[id]
929+
resolved := doc.Components.Callbacks[id]
936930
if resolved == nil {
937931
return failedToResolveRefFragmentPart(ref, id)
938932
}
@@ -1022,15 +1016,13 @@ func (loader *Loader) resolvePathItemRef(doc *T, entrypoint string, pathItem *Pa
10221016
}
10231017
id := unescapeRefString(rest)
10241018

1025-
definitions := doc.Paths
1026-
if definitions == nil {
1019+
if doc.Paths == nil {
10271020
return failedToResolveRefFragmentPart(ref, "paths")
10281021
}
1029-
resolved := definitions[id]
1022+
resolved := doc.Paths[id]
10301023
if resolved == nil {
10311024
return failedToResolveRefFragmentPart(ref, id)
10321025
}
1033-
10341026
*pathItem = *resolved
10351027
}
10361028
}

openapi3/openapi3.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
3636
}
3737

3838
func (doc *T) AddOperation(path string, method string, operation *Operation) {
39-
paths := doc.Paths
40-
if paths == nil {
41-
paths = make(Paths)
42-
doc.Paths = paths
39+
if doc.Paths == nil {
40+
doc.Paths = make(Paths)
4341
}
44-
pathItem := paths[path]
42+
pathItem := doc.Paths[path]
4543
if pathItem == nil {
4644
pathItem = &PathItem{}
47-
paths[path] = pathItem
45+
doc.Paths[path] = pathItem
4846
}
4947
pathItem.SetOperation(method, operation)
5048
}

openapi3/paths.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
2929
}
3030

3131
if pathItem == nil {
32-
paths[path] = &PathItem{}
33-
pathItem = paths[path]
32+
pathItem = &PathItem{}
33+
paths[path] = pathItem
3434
}
3535

3636
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
@@ -109,6 +109,37 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
109109
return nil
110110
}
111111

112+
// InMatchingOrder returns paths in the order they are matched against URLs.
113+
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
114+
// When matching URLs, concrete (non-templated) paths would be matched
115+
// before their templated counterparts.
116+
func (paths Paths) InMatchingOrder() []string {
117+
// NOTE: sorting by number of variables ASC then by descending lexicographical
118+
// order seems to be a good heuristic.
119+
if paths == nil {
120+
return nil
121+
}
122+
123+
vars := make(map[int][]string)
124+
max := 0
125+
for path := range paths {
126+
count := strings.Count(path, "}")
127+
vars[count] = append(vars[count], path)
128+
if count > max {
129+
max = count
130+
}
131+
}
132+
133+
ordered := make([]string, 0, len(paths))
134+
for c := 0; c <= max; c++ {
135+
if ps, ok := vars[c]; ok {
136+
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
137+
ordered = append(ordered, ps...)
138+
}
139+
}
140+
return ordered
141+
}
142+
112143
// Find returns a path that matches the key.
113144
//
114145
// The method ignores differences in template variable names (except possible "*" suffix).

routers/gorillamux/router.go

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
5656

5757
muxRouter := mux.NewRouter().UseEncodedPath()
5858
r := &Router{}
59-
for _, path := range orderedPaths(doc.Paths) {
59+
for _, path := range doc.Paths.InMatchingOrder() {
6060
pathItem := doc.Paths[path]
6161
if len(pathItem.Servers) > 0 {
6262
if servers, err = makeServers(pathItem.Servers); err != nil {
@@ -203,31 +203,6 @@ func newSrv(serverURL string, server *openapi3.Server, varsUpdater varsf) (srv,
203203
return svr, nil
204204
}
205205

206-
func orderedPaths(paths map[string]*openapi3.PathItem) []string {
207-
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject
208-
// When matching URLs, concrete (non-templated) paths would be matched
209-
// before their templated counterparts.
210-
// NOTE: sorting by number of variables ASC then by descending lexicographical
211-
// order seems to be a good heuristic.
212-
vars := make(map[int][]string)
213-
max := 0
214-
for path := range paths {
215-
count := strings.Count(path, "}")
216-
vars[count] = append(vars[count], path)
217-
if count > max {
218-
max = count
219-
}
220-
}
221-
ordered := make([]string, 0, len(paths))
222-
for c := 0; c <= max; c++ {
223-
if ps, ok := vars[c]; ok {
224-
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
225-
ordered = append(ordered, ps...)
226-
}
227-
}
228-
return ordered
229-
}
230-
231206
// Magic strings that temporarily replace "{}" so net/url.Parse() works
232207
var blURL, brURL = strings.Repeat("-", 50), strings.Repeat("_", 50)
233208

0 commit comments

Comments
 (0)