Skip to content

Commit

Permalink
openapi3: introduce (Paths).InMatchingOrder() paths iterator (#719)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenollp committed Dec 19, 2022
1 parent de2455e commit 1490eae
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Expand Up @@ -105,7 +105,7 @@ jobs:
- if: runner.os == 'Linux'
name: Missing specification object link to definition
run: |
[[ 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) ]]
[[ 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) ]]
- if: runner.os == 'Linux'
name: Style around ExtensionProps embedding
Expand Down
10 changes: 4 additions & 6 deletions openapi2/openapi2.go
Expand Up @@ -40,15 +40,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
}

func (doc *T) AddOperation(path string, method string, operation *Operation) {
paths := doc.Paths
if paths == nil {
paths = make(map[string]*PathItem)
doc.Paths = paths
if doc.Paths == nil {
doc.Paths = make(map[string]*PathItem)
}
pathItem := paths[path]
pathItem := doc.Paths[path]
if pathItem == nil {
pathItem = &PathItem{}
paths[path] = pathItem
doc.Paths[path] = pathItem
}
pathItem.SetOperation(method, operation)
}
Expand Down
10 changes: 4 additions & 6 deletions openapi2conv/openapi2_conv.go
Expand Up @@ -1204,15 +1204,13 @@ func stripNonCustomExtensions(extensions map[string]interface{}) {
}

func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) {
paths := doc2.Paths
if paths == nil {
paths = make(map[string]*openapi2.PathItem)
doc2.Paths = paths
if doc2.Paths == nil {
doc2.Paths = make(map[string]*openapi2.PathItem)
}
pathItem := paths[path]
pathItem := doc2.Paths[path]
if pathItem == nil {
pathItem = &openapi2.PathItem{}
paths[path] = pathItem
doc2.Paths[path] = pathItem
}
pathItem.ExtensionProps = extensionProps
}
5 changes: 1 addition & 4 deletions openapi3/internalize_refs.go
Expand Up @@ -193,14 +193,11 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver,
return false
}
name := refNameResolver(c.Ref)
if _, ok := doc.Components.Callbacks[name]; ok {
c.Ref = "#/components/callbacks/" + name
}
if doc.Components.Callbacks == nil {
doc.Components.Callbacks = make(Callbacks)
}
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
c.Ref = "#/components/callbacks/" + name
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
return true
}

Expand Down
18 changes: 5 additions & 13 deletions openapi3/loader.go
Expand Up @@ -281,12 +281,7 @@ func isSingleRefElement(ref string) bool {
return !strings.Contains(ref, "#")
}

func (loader *Loader) resolveComponent(
doc *T,
ref string,
path *url.URL,
resolved interface{},
) (
func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved interface{}) (
componentDoc *T,
componentPath *url.URL,
err error,
Expand Down Expand Up @@ -928,11 +923,10 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
}
id := unescapeRefString(rest)

definitions := doc.Components.Callbacks
if definitions == nil {
if doc.Components.Callbacks == nil {
return failedToResolveRefFragmentPart(ref, "callbacks")
}
resolved := definitions[id]
resolved := doc.Components.Callbacks[id]
if resolved == nil {
return failedToResolveRefFragmentPart(ref, id)
}
Expand Down Expand Up @@ -1022,15 +1016,13 @@ func (loader *Loader) resolvePathItemRef(doc *T, entrypoint string, pathItem *Pa
}
id := unescapeRefString(rest)

definitions := doc.Paths
if definitions == nil {
if doc.Paths == nil {
return failedToResolveRefFragmentPart(ref, "paths")
}
resolved := definitions[id]
resolved := doc.Paths[id]
if resolved == nil {
return failedToResolveRefFragmentPart(ref, id)
}

*pathItem = *resolved
}
}
Expand Down
10 changes: 4 additions & 6 deletions openapi3/openapi3.go
Expand Up @@ -36,15 +36,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
}

func (doc *T) AddOperation(path string, method string, operation *Operation) {
paths := doc.Paths
if paths == nil {
paths = make(Paths)
doc.Paths = paths
if doc.Paths == nil {
doc.Paths = make(Paths)
}
pathItem := paths[path]
pathItem := doc.Paths[path]
if pathItem == nil {
pathItem = &PathItem{}
paths[path] = pathItem
doc.Paths[path] = pathItem
}
pathItem.SetOperation(method, operation)
}
Expand Down
35 changes: 33 additions & 2 deletions openapi3/paths.go
Expand Up @@ -29,8 +29,8 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
}

if pathItem == nil {
paths[path] = &PathItem{}
pathItem = paths[path]
pathItem = &PathItem{}
paths[path] = pathItem
}

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

// InMatchingOrder returns paths in the order they are matched against URLs.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
// When matching URLs, concrete (non-templated) paths would be matched
// before their templated counterparts.
func (paths Paths) InMatchingOrder() []string {
// NOTE: sorting by number of variables ASC then by descending lexicographical
// order seems to be a good heuristic.
if paths == nil {
return nil
}

vars := make(map[int][]string)
max := 0
for path := range paths {
count := strings.Count(path, "}")
vars[count] = append(vars[count], path)
if count > max {
max = count
}
}

ordered := make([]string, 0, len(paths))
for c := 0; c <= max; c++ {
if ps, ok := vars[c]; ok {
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
ordered = append(ordered, ps...)
}
}
return ordered
}

// Find returns a path that matches the key.
//
// The method ignores differences in template variable names (except possible "*" suffix).
Expand Down
27 changes: 1 addition & 26 deletions routers/gorillamux/router.go
Expand Up @@ -56,7 +56,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {

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

func orderedPaths(paths map[string]*openapi3.PathItem) []string {
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject
// When matching URLs, concrete (non-templated) paths would be matched
// before their templated counterparts.
// NOTE: sorting by number of variables ASC then by descending lexicographical
// order seems to be a good heuristic.
vars := make(map[int][]string)
max := 0
for path := range paths {
count := strings.Count(path, "}")
vars[count] = append(vars[count], path)
if count > max {
max = count
}
}
ordered := make([]string, 0, len(paths))
for c := 0; c <= max; c++ {
if ps, ok := vars[c]; ok {
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
ordered = append(ordered, ps...)
}
}
return ordered
}

// Magic strings that temporarily replace "{}" so net/url.Parse() works
var blURL, brURL = strings.Repeat("-", 50), strings.Repeat("_", 50)

Expand Down

0 comments on commit 1490eae

Please sign in to comment.