fix: error handling and vendor
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-08-04 10:55:35 +02:00
parent 8603738b38
commit 9e9227b420
1193 changed files with 283471 additions and 1 deletions

View File

@ -0,0 +1,41 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package debug
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
)
var (
output = os.Stdout
)
// GetLogger provides a prefix debug logger
func GetLogger(prefix string, debug bool) func(string, ...interface{}) {
if debug {
logger := log.New(output, prefix+":", log.LstdFlags)
return func(msg string, args ...interface{}) {
_, file1, pos1, _ := runtime.Caller(1)
logger.Printf("%s:%d: %s", filepath.Base(file1), pos1, fmt.Sprintf(msg, args...))
}
}
return func(_ string, _ ...interface{}) {}
}

View File

@ -0,0 +1,87 @@
package normalize
import (
"net/url"
"path"
"path/filepath"
"strings"
"github.com/go-openapi/spec"
)
// RebaseRef rebases a remote ref relative to a base ref.
//
// NOTE: does not support JSONschema ID for $ref (we assume we are working with swagger specs here).
//
// NOTE(windows):
// * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// * "/ in paths may appear as escape sequences
func RebaseRef(baseRef string, ref string) string {
baseRef, _ = url.PathUnescape(baseRef)
ref, _ = url.PathUnescape(ref)
if baseRef == "" || baseRef == "." || strings.HasPrefix(baseRef, "#") {
return ref
}
parts := strings.Split(ref, "#")
baseParts := strings.Split(baseRef, "#")
baseURL, _ := url.Parse(baseParts[0])
if strings.HasPrefix(ref, "#") {
if baseURL.Host == "" {
return strings.Join([]string{baseParts[0], parts[1]}, "#")
}
return strings.Join([]string{baseParts[0], parts[1]}, "#")
}
refURL, _ := url.Parse(parts[0])
if refURL.Host != "" || filepath.IsAbs(parts[0]) {
// not rebasing an absolute path
return ref
}
// there is a relative path
var basePath string
if baseURL.Host != "" {
// when there is a host, standard URI rules apply (with "/")
baseURL.Path = path.Dir(baseURL.Path)
baseURL.Path = path.Join(baseURL.Path, "/"+parts[0])
return baseURL.String()
}
// this is a local relative path
// basePart[0] and parts[0] are local filesystem directories/files
basePath = filepath.Dir(baseParts[0])
relPath := filepath.Join(basePath, string(filepath.Separator)+parts[0])
if len(parts) > 1 {
return strings.Join([]string{relPath, parts[1]}, "#")
}
return relPath
}
// Path renders absolute path on remote file refs
//
// NOTE(windows):
// * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// * "/ in paths may appear as escape sequences
func Path(ref spec.Ref, basePath string) string {
uri, _ := url.PathUnescape(ref.String())
if ref.HasFragmentOnly || filepath.IsAbs(uri) {
return uri
}
refURL, _ := url.Parse(uri)
if refURL.Host != "" {
return uri
}
parts := strings.Split(uri, "#")
// BasePath, parts[0] are local filesystem directories, guaranteed to be absolute at this stage
parts[0] = filepath.Join(filepath.Dir(basePath), parts[0])
return strings.Join(parts, "#")
}

View File

@ -0,0 +1,90 @@
package operations
import (
"path"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
// AllOpRefsByRef returns an index of sortable operations
func AllOpRefsByRef(specDoc Provider, operationIDs []string) map[string]OpRef {
return OpRefsByRef(GatherOperations(specDoc, operationIDs))
}
// OpRefsByRef indexes a map of sortable operations
func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef {
result := make(map[string]OpRef, len(oprefs))
for _, v := range oprefs {
result[v.Ref.String()] = v
}
return result
}
// OpRef is an indexable, sortable operation
type OpRef struct {
Method string
Path string
Key string
ID string
Op *spec.Operation
Ref spec.Ref
}
// OpRefs is a sortable collection of operations
type OpRefs []OpRef
func (o OpRefs) Len() int { return len(o) }
func (o OpRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o OpRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
// Provider knows how to collect operations from a spec
type Provider interface {
Operations() map[string]map[string]*spec.Operation
}
// GatherOperations builds a map of sorted operations from a spec
func GatherOperations(specDoc Provider, operationIDs []string) map[string]OpRef {
var oprefs OpRefs
for method, pathItem := range specDoc.Operations() {
for pth, operation := range pathItem {
vv := *operation
oprefs = append(oprefs, OpRef{
Key: swag.ToGoName(strings.ToLower(method) + " " + pth),
Method: method,
Path: pth,
ID: vv.ID,
Op: &vv,
Ref: spec.MustCreateRef("#" + path.Join("/paths", jsonpointer.Escape(pth), method)),
})
}
}
sort.Sort(oprefs)
operations := make(map[string]OpRef)
for _, opr := range oprefs {
nm := opr.ID
if nm == "" {
nm = opr.Key
}
oo, found := operations[nm]
if found && oo.Method != opr.Method && oo.Path != opr.Path {
nm = opr.Key
}
if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) {
opr.ID = nm
opr.Op.ID = nm
operations[nm] = opr
}
}
return operations
}

View File

@ -0,0 +1,458 @@
package replace
import (
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"strconv"
"github.com/go-openapi/analysis/internal/debug"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/spec"
)
const definitionsPath = "#/definitions"
var debugLog = debug.GetLogger("analysis/flatten/replace", os.Getenv("SWAGGER_DEBUG") != "")
// RewriteSchemaToRef replaces a schema with a Ref
func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
debugLog("rewriting schema to ref for %s with %s", key, ref.String())
_, value, err := getPointerFromKey(sp, key)
if err != nil {
return err
}
switch refable := value.(type) {
case *spec.Schema:
return rewriteParentRef(sp, key, ref)
case spec.Schema:
return rewriteParentRef(sp, key, ref)
case *spec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
}
case *spec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
}
case map[string]interface{}: // this happens e.g. if a schema points to an extension unmarshaled as map[string]interface{}
return rewriteParentRef(sp, key, ref)
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
parent, entry, pvalue, err := getParentFromKey(sp, key)
if err != nil {
return err
}
debugLog("rewriting holder for %T", pvalue)
switch container := pvalue.(type) {
case spec.Response:
if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
return err
}
case *spec.Response:
container.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *spec.Responses:
statusCode, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
resp := container.StatusCodeResponses[statusCode]
resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
container.StatusCodeResponses[statusCode] = resp
case map[string]spec.Response:
resp := container[entry]
resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
container[entry] = resp
case spec.Parameter:
if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
return err
}
case map[string]spec.Parameter:
param := container[entry]
param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
container[entry] = param
case []spec.Parameter:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
param := container[idx]
param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
container[idx] = param
case spec.Definitions:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case map[string]spec.Schema:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *spec.SchemaOrArray:
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case spec.SchemaProperties:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *interface{}:
*container = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
}
return nil
}
// getPointerFromKey retrieves the content of the JSON pointer "key"
func getPointerFromKey(sp interface{}, key string) (string, interface{}, error) {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic("unexpected type used in getPointerFromKey")
}
if key == "#/" {
return "", sp, nil
}
// unescape chars in key, e.g. "{}" from path params
pth, _ := url.PathUnescape(key[1:])
ptr, err := jsonpointer.New(pth)
if err != nil {
return "", nil, err
}
value, _, err := ptr.Get(sp)
if err != nil {
debugLog("error when getting key: %s with path: %s", key, pth)
return "", nil, err
}
return pth, value, nil
}
// getParentFromKey retrieves the container of the JSON pointer "key"
func getParentFromKey(sp interface{}, key string) (string, string, interface{}, error) {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic("unexpected type used in getPointerFromKey")
}
// unescape chars in key, e.g. "{}" from path params
pth, _ := url.PathUnescape(key[1:])
parent, entry := path.Dir(pth), path.Base(pth)
debugLog("getting schema holder at: %s, with entry: %s", parent, entry)
pptr, err := jsonpointer.New(parent)
if err != nil {
return "", "", nil, err
}
pvalue, _, err := pptr.Get(sp)
if err != nil {
return "", "", nil, fmt.Errorf("can't get parent for %s: %w", parent, err)
}
return parent, entry, pvalue, nil
}
// UpdateRef replaces a ref by another one
func UpdateRef(sp interface{}, key string, ref spec.Ref) error {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic("unexpected type used in getPointerFromKey")
}
debugLog("updating ref for %s with %s", key, ref.String())
pth, value, err := getPointerFromKey(sp, key)
if err != nil {
return err
}
switch refable := value.(type) {
case *spec.Schema:
refable.Ref = ref
case *spec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case *spec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case spec.Schema:
debugLog("rewriting holder for %T", refable)
_, entry, pvalue, erp := getParentFromKey(sp, key)
if erp != nil {
return err
}
switch container := pvalue.(type) {
case spec.Definitions:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case map[string]spec.Schema:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", pth, err)
}
container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *spec.SchemaOrArray:
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", pth, err)
}
container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case spec.SchemaProperties:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return fmt.Errorf("unhandled container type at %s: %T", key, value)
}
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
// UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema)
func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
debugLog("updating ref for %s with schema", key)
pth, value, err := getPointerFromKey(sp, key)
if err != nil {
return err
}
switch refable := value.(type) {
case *spec.Schema:
*refable = *sch
case spec.Schema:
_, entry, pvalue, erp := getParentFromKey(sp, key)
if erp != nil {
return err
}
switch container := pvalue.(type) {
case spec.Definitions:
container[entry] = *sch
case map[string]spec.Schema:
container[entry] = *sch
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", pth, err)
}
container[idx] = *sch
case *spec.SchemaOrArray:
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %w", pth, err)
}
container.Schemas[idx] = *sch
case spec.SchemaProperties:
container[entry] = *sch
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value)
}
case *spec.SchemaOrArray:
*refable.Schema = *sch
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
case *spec.SchemaOrBool:
*refable.Schema = *sch
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
// DeepestRefResult holds the results from DeepestRef analysis
type DeepestRefResult struct {
Ref spec.Ref
Schema *spec.Schema
Warnings []string
}
// DeepestRef finds the first definition ref, from a cascade of nested refs which are not definitions.
// - if no definition is found, returns the deepest ref.
// - pointers to external files are expanded
//
// NOTE: all external $ref's are assumed to be already expanded at this stage.
func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) {
if !ref.HasFragmentOnly {
// we found an external $ref, which is odd at this stage:
// do nothing on external $refs
return &DeepestRefResult{Ref: ref}, nil
}
currentRef := ref
visited := make(map[string]bool, 64)
warnings := make([]string, 0, 2)
DOWNREF:
for currentRef.String() != "" {
if path.Dir(currentRef.String()) == definitionsPath {
// this is a top-level definition: stop here and return this ref
return &DeepestRefResult{Ref: currentRef}, nil
}
if _, beenThere := visited[currentRef.String()]; beenThere {
return nil,
fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String())
}
visited[currentRef.String()] = true
value, _, err := currentRef.GetPointer().Get(sp)
if err != nil {
return nil, err
}
switch refable := value.(type) {
case *spec.Schema:
if refable.Ref.String() == "" {
break DOWNREF
}
currentRef = refable.Ref
case spec.Schema:
if refable.Ref.String() == "" {
break DOWNREF
}
currentRef = refable.Ref
case *spec.SchemaOrArray:
if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
break DOWNREF
}
currentRef = refable.Schema.Ref
case *spec.SchemaOrBool:
if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
break DOWNREF
}
currentRef = refable.Schema.Ref
case spec.Response:
// a pointer points to a schema initially marshalled in responses section...
// Attempt to convert this to a schema. If this fails, the spec is invalid
asJSON, _ := refable.MarshalJSON()
var asSchema spec.Schema
err := asSchema.UnmarshalJSON(asJSON)
if err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
if asSchema.Ref.String() == "" {
break DOWNREF
}
currentRef = asSchema.Ref
case spec.Parameter:
// a pointer points to a schema initially marshalled in parameters section...
// Attempt to convert this to a schema. If this fails, the spec is invalid
asJSON, _ := refable.MarshalJSON()
var asSchema spec.Schema
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
if asSchema.Ref.String() == "" {
break DOWNREF
}
currentRef = asSchema.Ref
default:
// fallback: attempts to resolve the pointer as a schema
if refable == nil {
break DOWNREF
}
asJSON, _ := json.Marshal(refable)
var asSchema spec.Schema
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable))
if asSchema.Ref.String() == "" {
break DOWNREF
}
currentRef = asSchema.Ref
}
}
// assess what schema we're ending with
sch, erv := spec.ResolveRefWithBase(sp, &currentRef, opts)
if erv != nil {
return nil, erv
}
if sch == nil {
return nil, fmt.Errorf("no schema found at %s", currentRef.String())
}
return &DeepestRefResult{Ref: currentRef, Schema: sch, Warnings: warnings}, nil
}

View File

@ -0,0 +1,29 @@
// Package schutils provides tools to save or clone a schema
// when flattening a spec.
package schutils
import (
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
// Save registers a schema as an entry in spec #/definitions
func Save(sp *spec.Swagger, name string, schema *spec.Schema) {
if schema == nil {
return
}
if sp.Definitions == nil {
sp.Definitions = make(map[string]spec.Schema, 150)
}
sp.Definitions[name] = *schema
}
// Clone deep-clones a schema
func Clone(schema *spec.Schema) *spec.Schema {
var sch spec.Schema
_ = swag.FromDynamicJSON(schema, &sch)
return &sch
}

View File

@ -0,0 +1,201 @@
package sortref
import (
"net/http"
"path"
"strconv"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/spec"
)
const (
paths = "paths"
responses = "responses"
parameters = "parameters"
definitions = "definitions"
)
var (
ignoredKeys map[string]struct{}
validMethods map[string]struct{}
)
func init() {
ignoredKeys = map[string]struct{}{
"schema": {},
"properties": {},
"not": {},
"anyOf": {},
"oneOf": {},
}
validMethods = map[string]struct{}{
"GET": {},
"HEAD": {},
"OPTIONS": {},
"PATCH": {},
"POST": {},
"PUT": {},
"DELETE": {},
}
}
// Key represent a key item constructed from /-separated segments
type Key struct {
Segments int
Key string
}
// Keys is a sortable collable collection of Keys
type Keys []Key
func (k Keys) Len() int { return len(k) }
func (k Keys) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k Keys) Less(i, j int) bool {
return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key)
}
// KeyParts construct a SplitKey with all its /-separated segments decomposed. It is sortable.
func KeyParts(key string) SplitKey {
var res []string
for _, part := range strings.Split(key[1:], "/") {
if part != "" {
res = append(res, jsonpointer.Unescape(part))
}
}
return res
}
// SplitKey holds of the parts of a /-separated key, so that their location may be determined.
type SplitKey []string
// IsDefinition is true when the split key is in the #/definitions section of a spec
func (s SplitKey) IsDefinition() bool {
return len(s) > 1 && s[0] == definitions
}
// DefinitionName yields the name of the definition
func (s SplitKey) DefinitionName() string {
if !s.IsDefinition() {
return ""
}
return s[1]
}
func (s SplitKey) isKeyName(i int) bool {
if i <= 0 {
return false
}
count := 0
for idx := i - 1; idx > 0; idx-- {
if s[idx] != "properties" {
break
}
count++
}
return count%2 != 0
}
// PartAdder know how to construct the components of a new name
type PartAdder func(string) []string
// BuildName builds a name from segments
func (s SplitKey) BuildName(segments []string, startIndex int, adder PartAdder) string {
for i, part := range s[startIndex:] {
if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) {
segments = append(segments, adder(part)...)
}
}
return strings.Join(segments, " ")
}
// IsOperation is true when the split key is in the operations section
func (s SplitKey) IsOperation() bool {
return len(s) > 1 && s[0] == paths
}
// IsSharedOperationParam is true when the split key is in the parameters section of a path
func (s SplitKey) IsSharedOperationParam() bool {
return len(s) > 2 && s[0] == paths && s[2] == parameters
}
// IsSharedParam is true when the split key is in the #/parameters section of a spec
func (s SplitKey) IsSharedParam() bool {
return len(s) > 1 && s[0] == parameters
}
// IsOperationParam is true when the split key is in the parameters section of an operation
func (s SplitKey) IsOperationParam() bool {
return len(s) > 3 && s[0] == paths && s[3] == parameters
}
// IsOperationResponse is true when the split key is in the responses section of an operation
func (s SplitKey) IsOperationResponse() bool {
return len(s) > 3 && s[0] == paths && s[3] == responses
}
// IsSharedResponse is true when the split key is in the #/responses section of a spec
func (s SplitKey) IsSharedResponse() bool {
return len(s) > 1 && s[0] == responses
}
// IsDefaultResponse is true when the split key is the default response for an operation
func (s SplitKey) IsDefaultResponse() bool {
return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default"
}
// IsStatusCodeResponse is true when the split key is an operation response with a status code
func (s SplitKey) IsStatusCodeResponse() bool {
isInt := func() bool {
_, err := strconv.Atoi(s[4])
return err == nil
}
return len(s) > 4 && s[0] == paths && s[3] == responses && isInt()
}
// ResponseName yields either the status code or "Default" for a response
func (s SplitKey) ResponseName() string {
if s.IsStatusCodeResponse() {
code, _ := strconv.Atoi(s[4])
return http.StatusText(code)
}
if s.IsDefaultResponse() {
return "Default"
}
return ""
}
// PathItemRef constructs a $ref object from a split key of the form /{path}/{method}
func (s SplitKey) PathItemRef() spec.Ref {
if len(s) < 3 {
return spec.Ref{}
}
pth, method := s[1], s[2]
if _, isValidMethod := validMethods[strings.ToUpper(method)]; !isValidMethod && !strings.HasPrefix(method, "x-") {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method)))
}
// PathRef constructs a $ref object from a split key of the form /paths/{reference}
func (s SplitKey) PathRef() spec.Ref {
if !s.IsOperation() {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(s[1])))
}

View File

@ -0,0 +1,141 @@
package sortref
import (
"reflect"
"sort"
"strings"
"github.com/go-openapi/analysis/internal/flatten/normalize"
"github.com/go-openapi/spec"
)
var depthGroupOrder = []string{
"sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition",
}
type mapIterator struct {
len int
mapIter *reflect.MapIter
}
func (i *mapIterator) Next() bool {
return i.mapIter.Next()
}
func (i *mapIterator) Len() int {
return i.len
}
func (i *mapIterator) Key() string {
return i.mapIter.Key().String()
}
func mustMapIterator(anyMap interface{}) *mapIterator {
val := reflect.ValueOf(anyMap)
return &mapIterator{mapIter: val.MapRange(), len: val.Len()}
}
// DepthFirst sorts a map of anything. It groups keys by category
// (shared params, op param, statuscode response, default response, definitions)
// sort groups internally by number of parts in the key and lexical names
// flatten groups into a single list of keys
func DepthFirst(in interface{}) []string {
iterator := mustMapIterator(in)
sorted := make([]string, 0, iterator.Len())
grouped := make(map[string]Keys, iterator.Len())
for iterator.Next() {
k := iterator.Key()
split := KeyParts(k)
var pk string
if split.IsSharedOperationParam() {
pk = "sharedOpParam"
}
if split.IsOperationParam() {
pk = "opParam"
}
if split.IsStatusCodeResponse() {
pk = "codeResponse"
}
if split.IsDefaultResponse() {
pk = "defaultResponse"
}
if split.IsDefinition() {
pk = "definition"
}
if split.IsSharedParam() {
pk = "sharedParam"
}
if split.IsSharedResponse() {
pk = "sharedResponse"
}
grouped[pk] = append(grouped[pk], Key{Segments: len(split), Key: k})
}
for _, pk := range depthGroupOrder {
res := grouped[pk]
sort.Sort(res)
for _, v := range res {
sorted = append(sorted, v.Key)
}
}
return sorted
}
// topMostRefs is able to sort refs by hierarchical then lexicographic order,
// yielding refs ordered breadth-first.
type topmostRefs []string
func (k topmostRefs) Len() int { return len(k) }
func (k topmostRefs) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k topmostRefs) Less(i, j int) bool {
li, lj := len(strings.Split(k[i], "/")), len(strings.Split(k[j], "/"))
if li == lj {
return k[i] < k[j]
}
return li < lj
}
// TopmostFirst sorts references by depth
func TopmostFirst(refs []string) []string {
res := topmostRefs(refs)
sort.Sort(res)
return res
}
// RefRevIdx is a reverse index for references
type RefRevIdx struct {
Ref spec.Ref
Keys []string
}
// ReverseIndex builds a reverse index for references in schemas
func ReverseIndex(schemas map[string]spec.Ref, basePath string) map[string]RefRevIdx {
collected := make(map[string]RefRevIdx)
for key, schRef := range schemas {
// normalize paths before sorting,
// so we get together keys that are from the same external file
normalizedPath := normalize.Path(schRef, basePath)
entry, ok := collected[normalizedPath]
if ok {
entry.Keys = append(entry.Keys, key)
collected[normalizedPath] = entry
continue
}
collected[normalizedPath] = RefRevIdx{
Ref: schRef,
Keys: []string{key},
}
}
return collected
}