mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-06 01:38:10 +00:00
Travis seems to be having issues pulling deps, so we'll have to check in the vendor directory and prevent the makefile from trying to regenerate it normally.
1048 lines
28 KiB
Go
1048 lines
28 KiB
Go
// 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 spec
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/go-openapi/jsonpointer"
|
|
"github.com/go-openapi/swag"
|
|
)
|
|
|
|
var (
|
|
// Debug enables logging when SWAGGER_DEBUG env var is not empty
|
|
Debug = os.Getenv("SWAGGER_DEBUG") != ""
|
|
)
|
|
|
|
// ExpandOptions provides options for expand.
|
|
type ExpandOptions struct {
|
|
RelativeBase string
|
|
SkipSchemas bool
|
|
ContinueOnError bool
|
|
}
|
|
|
|
// ResolutionCache a cache for resolving urls
|
|
type ResolutionCache interface {
|
|
Get(string) (interface{}, bool)
|
|
Set(string, interface{})
|
|
}
|
|
|
|
type simpleCache struct {
|
|
lock sync.Mutex
|
|
store map[string]interface{}
|
|
}
|
|
|
|
var resCache ResolutionCache
|
|
|
|
func init() {
|
|
resCache = initResolutionCache()
|
|
}
|
|
|
|
func initResolutionCache() ResolutionCache {
|
|
return &simpleCache{store: map[string]interface{}{
|
|
"http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(),
|
|
"http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(),
|
|
}}
|
|
}
|
|
|
|
func (s *simpleCache) Get(uri string) (interface{}, bool) {
|
|
debugLog("getting %q from resolution cache", uri)
|
|
s.lock.Lock()
|
|
v, ok := s.store[uri]
|
|
debugLog("got %q from resolution cache: %t", uri, ok)
|
|
|
|
s.lock.Unlock()
|
|
return v, ok
|
|
}
|
|
|
|
func (s *simpleCache) Set(uri string, data interface{}) {
|
|
s.lock.Lock()
|
|
s.store[uri] = data
|
|
s.lock.Unlock()
|
|
}
|
|
|
|
// ResolveRefWithBase resolves a reference against a context root with preservation of base path
|
|
func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) {
|
|
resolver, err := defaultSchemaLoader(root, opts, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
specBasePath := ""
|
|
if opts != nil && opts.RelativeBase != "" {
|
|
specBasePath, _ = absPath(opts.RelativeBase)
|
|
}
|
|
|
|
result := new(Schema)
|
|
if err := resolver.Resolve(ref, result, specBasePath); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ResolveRef resolves a reference against a context root
|
|
// ref is guaranteed to be in root (no need to go to external files)
|
|
// ResolveRef is ONLY called from the code generation module
|
|
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
|
|
res, _, err := ref.GetPointer().Get(root)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
switch sch := res.(type) {
|
|
case Schema:
|
|
return &sch, nil
|
|
case *Schema:
|
|
return sch, nil
|
|
case map[string]interface{}:
|
|
b, _ := json.Marshal(sch)
|
|
newSch := new(Schema)
|
|
json.Unmarshal(b, newSch)
|
|
return newSch, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown type for the resolved reference")
|
|
}
|
|
}
|
|
|
|
// ResolveParameter resolves a paramter reference against a context root
|
|
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
|
|
return ResolveParameterWithBase(root, ref, nil)
|
|
}
|
|
|
|
// ResolveParameterWithBase resolves a paramter reference against a context root and base path
|
|
func ResolveParameterWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Parameter, error) {
|
|
resolver, err := defaultSchemaLoader(root, opts, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := new(Parameter)
|
|
if err := resolver.Resolve(&ref, result, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ResolveResponse resolves response a reference against a context root
|
|
func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
|
|
return ResolveResponseWithBase(root, ref, nil)
|
|
}
|
|
|
|
// ResolveResponseWithBase resolves response a reference against a context root and base path
|
|
func ResolveResponseWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Response, error) {
|
|
resolver, err := defaultSchemaLoader(root, opts, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := new(Response)
|
|
if err := resolver.Resolve(&ref, result, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ResolveItems resolves header and parameter items reference against a context root and base path
|
|
func ResolveItems(root interface{}, ref Ref, opts *ExpandOptions) (*Items, error) {
|
|
resolver, err := defaultSchemaLoader(root, opts, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
basePath := ""
|
|
if opts.RelativeBase != "" {
|
|
basePath = opts.RelativeBase
|
|
}
|
|
result := new(Items)
|
|
if err := resolver.Resolve(&ref, result, basePath); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ResolvePathItem resolves response a path item against a context root and base path
|
|
func ResolvePathItem(root interface{}, ref Ref, opts *ExpandOptions) (*PathItem, error) {
|
|
resolver, err := defaultSchemaLoader(root, opts, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
basePath := ""
|
|
if opts.RelativeBase != "" {
|
|
basePath = opts.RelativeBase
|
|
}
|
|
result := new(PathItem)
|
|
if err := resolver.Resolve(&ref, result, basePath); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type schemaLoader struct {
|
|
root interface{}
|
|
options *ExpandOptions
|
|
cache ResolutionCache
|
|
loadDoc func(string) (json.RawMessage, error)
|
|
}
|
|
|
|
var idPtr, _ = jsonpointer.New("/id")
|
|
var refPtr, _ = jsonpointer.New("/$ref")
|
|
|
|
// PathLoader function to use when loading remote refs
|
|
var PathLoader func(string) (json.RawMessage, error)
|
|
|
|
func init() {
|
|
PathLoader = func(path string) (json.RawMessage, error) {
|
|
data, err := swag.LoadFromFileOrHTTP(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return json.RawMessage(data), nil
|
|
}
|
|
}
|
|
|
|
func defaultSchemaLoader(
|
|
root interface{},
|
|
expandOptions *ExpandOptions,
|
|
cache ResolutionCache) (*schemaLoader, error) {
|
|
|
|
if cache == nil {
|
|
cache = resCache
|
|
}
|
|
if expandOptions == nil {
|
|
expandOptions = &ExpandOptions{}
|
|
}
|
|
|
|
return &schemaLoader{
|
|
root: root,
|
|
options: expandOptions,
|
|
cache: cache,
|
|
loadDoc: func(path string) (json.RawMessage, error) {
|
|
debugLog("fetching document at %q", path)
|
|
return PathLoader(path)
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func idFromNode(node interface{}) (*Ref, error) {
|
|
if idValue, _, err := idPtr.Get(node); err == nil {
|
|
if refStr, ok := idValue.(string); ok && refStr != "" {
|
|
idRef, err := NewRef(refStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &idRef, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointer) *Ref {
|
|
if startingRef == nil {
|
|
return nil
|
|
}
|
|
|
|
if ptr == nil {
|
|
return startingRef
|
|
}
|
|
|
|
ret := startingRef
|
|
var idRef *Ref
|
|
node := startingNode
|
|
|
|
for _, tok := range ptr.DecodedTokens() {
|
|
node, _, _ = jsonpointer.GetForToken(node, tok)
|
|
if node == nil {
|
|
break
|
|
}
|
|
|
|
idRef, _ = idFromNode(node)
|
|
if idRef != nil {
|
|
nw, err := ret.Inherits(*idRef)
|
|
if err != nil {
|
|
break
|
|
}
|
|
ret = nw
|
|
}
|
|
|
|
refRef, _, _ := refPtr.Get(node)
|
|
if refRef != nil {
|
|
var rf Ref
|
|
switch value := refRef.(type) {
|
|
case string:
|
|
rf, _ = NewRef(value)
|
|
}
|
|
nw, err := ret.Inherits(rf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
nwURL := nw.GetURL()
|
|
if nwURL.Scheme == "file" || (nwURL.Scheme == "" && nwURL.Host == "") {
|
|
nwpt := filepath.ToSlash(nwURL.Path)
|
|
if filepath.IsAbs(nwpt) {
|
|
_, err := os.Stat(nwpt)
|
|
if err != nil {
|
|
nwURL.Path = filepath.Join(".", nwpt)
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = nw
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func debugLog(msg string, args ...interface{}) {
|
|
if Debug {
|
|
log.Printf(msg, args...)
|
|
}
|
|
}
|
|
|
|
// normalize absolute path for cache.
|
|
// on Windows, drive letters should be converted to lower as scheme in net/url.URL
|
|
func normalizeAbsPath(path string) string {
|
|
u, err := url.Parse(path)
|
|
if err != nil {
|
|
debugLog("normalize absolute path failed: %s", err)
|
|
return path
|
|
}
|
|
return u.String()
|
|
}
|
|
|
|
// base or refPath could be a file path or a URL
|
|
// given a base absolute path and a ref path, return the absolute path of refPath
|
|
// 1) if refPath is absolute, return it
|
|
// 2) if refPath is relative, join it with basePath keeping the scheme, hosts, and ports if exists
|
|
// base could be a directory or a full file path
|
|
func normalizePaths(refPath, base string) string {
|
|
refURL, _ := url.Parse(refPath)
|
|
if path.IsAbs(refURL.Path) || filepath.IsAbs(refPath) {
|
|
// refPath is actually absolute
|
|
if refURL.Host != "" {
|
|
return refPath
|
|
}
|
|
parts := strings.Split(refPath, "#")
|
|
result := filepath.FromSlash(parts[0])
|
|
if len(parts) == 2 {
|
|
result += "#" + parts[1]
|
|
}
|
|
return result
|
|
}
|
|
|
|
// relative refPath
|
|
baseURL, _ := url.Parse(base)
|
|
if !strings.HasPrefix(refPath, "#") {
|
|
// combining paths
|
|
if baseURL.Host != "" {
|
|
baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
|
|
} else { // base is a file
|
|
newBase := fmt.Sprintf("%s#%s", filepath.Join(filepath.Dir(base), filepath.FromSlash(refURL.Path)), refURL.Fragment)
|
|
return newBase
|
|
}
|
|
|
|
}
|
|
// copying fragment from ref to base
|
|
baseURL.Fragment = refURL.Fragment
|
|
return baseURL.String()
|
|
}
|
|
|
|
// relativeBase could be an ABSOLUTE file path or an ABSOLUTE URL
|
|
func normalizeFileRef(ref *Ref, relativeBase string) *Ref {
|
|
// This is important for when the reference is pointing to the root schema
|
|
if ref.String() == "" {
|
|
r, _ := NewRef(relativeBase)
|
|
return &r
|
|
}
|
|
|
|
refURL := ref.GetURL()
|
|
debugLog("normalizing %s against %s (%s)", ref.String(), relativeBase, refURL.String())
|
|
|
|
s := normalizePaths(ref.String(), relativeBase)
|
|
r, _ := NewRef(s)
|
|
return &r
|
|
}
|
|
|
|
func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error {
|
|
tgt := reflect.ValueOf(target)
|
|
if tgt.Kind() != reflect.Ptr {
|
|
return fmt.Errorf("resolve ref: target needs to be a pointer")
|
|
}
|
|
|
|
refURL := ref.GetURL()
|
|
if refURL == nil {
|
|
return nil
|
|
}
|
|
|
|
var res interface{}
|
|
var data interface{}
|
|
var err error
|
|
// Resolve against the root if it isn't nil, and if ref is pointing at the root, or has a fragment only which means
|
|
// it is pointing somewhere in the root.
|
|
root := r.root
|
|
if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" {
|
|
if baseRef, err := NewRef(basePath); err == nil {
|
|
root, _, _, _ = r.load(baseRef.GetURL())
|
|
}
|
|
}
|
|
if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil {
|
|
data = root
|
|
} else {
|
|
baseRef := normalizeFileRef(ref, basePath)
|
|
debugLog("current ref is: %s", ref.String())
|
|
debugLog("current ref normalized file: %s", baseRef.String())
|
|
data, _, _, err = r.load(baseRef.GetURL())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
res = data
|
|
if ref.String() != "" {
|
|
res, _, err = ref.GetPointer().Get(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := swag.DynamicJSONToStruct(res, target); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
|
|
debugLog("loading schema from url: %s", refURL)
|
|
toFetch := *refURL
|
|
toFetch.Fragment = ""
|
|
|
|
normalized := normalizeAbsPath(toFetch.String())
|
|
|
|
data, fromCache := r.cache.Get(normalized)
|
|
if !fromCache {
|
|
b, err := r.loadDoc(normalized)
|
|
if err != nil {
|
|
return nil, url.URL{}, false, err
|
|
}
|
|
|
|
if err := json.Unmarshal(b, &data); err != nil {
|
|
return nil, url.URL{}, false, err
|
|
}
|
|
r.cache.Set(normalized, data)
|
|
}
|
|
|
|
return data, toFetch, fromCache, nil
|
|
}
|
|
|
|
// Resolve resolves a reference against basePath and stores the result in target
|
|
// Resolve is not in charge of following references, it only resolves ref by following its URL
|
|
// if the schema that ref is referring to has more refs in it. Resolve doesn't resolve them
|
|
// if basePath is an empty string, ref is resolved against the root schema stored in the schemaLoader struct
|
|
func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error {
|
|
return r.resolveRef(ref, target, basePath)
|
|
}
|
|
|
|
// absPath returns the absolute path of a file
|
|
func absPath(fname string) (string, error) {
|
|
if strings.HasPrefix(fname, "http") {
|
|
return fname, nil
|
|
}
|
|
if filepath.IsAbs(fname) {
|
|
return fname, nil
|
|
}
|
|
wd, err := os.Getwd()
|
|
return filepath.Join(wd, fname), err
|
|
}
|
|
|
|
// ExpandSpec expands the references in a swagger spec
|
|
func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
|
resolver, err := defaultSchemaLoader(spec, options, nil)
|
|
// Just in case this ever returns an error.
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
|
|
// getting the base path of the spec to adjust all subsequent reference resolutions
|
|
specBasePath := ""
|
|
if options != nil && options.RelativeBase != "" {
|
|
specBasePath, _ = absPath(options.RelativeBase)
|
|
}
|
|
|
|
if options == nil || !options.SkipSchemas {
|
|
for key, definition := range spec.Definitions {
|
|
var def *Schema
|
|
var err error
|
|
if def, err = expandSchema(definition, []string{fmt.Sprintf("#/definitions/%s", key)}, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if def != nil {
|
|
spec.Definitions[key] = *def
|
|
}
|
|
}
|
|
}
|
|
|
|
for key, parameter := range spec.Parameters {
|
|
if err := expandParameter(¶meter, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
spec.Parameters[key] = parameter
|
|
}
|
|
|
|
for key, response := range spec.Responses {
|
|
if err := expandResponse(&response, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
spec.Responses[key] = response
|
|
}
|
|
|
|
if spec.Paths != nil {
|
|
for key, path := range spec.Paths.Paths {
|
|
if err := expandPathItem(&path, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
spec.Paths.Paths[key] = path
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func shouldStopOnError(err error, opts *ExpandOptions) bool {
|
|
if err != nil && !opts.ContinueOnError {
|
|
return true
|
|
}
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// ExpandSchema expands the refs in the schema object with reference to the root object
|
|
// go-openapi/validate uses this function
|
|
// notice that it is impossible to reference a json scema in a different file other than root
|
|
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
|
|
// Only save the root to a tmp file if it isn't nil.
|
|
var base string
|
|
if root != nil {
|
|
base, _ = absPath("root")
|
|
if cache == nil {
|
|
cache = resCache
|
|
}
|
|
cache.Set(normalizeAbsPath(base), root)
|
|
base = "root"
|
|
}
|
|
|
|
opts := &ExpandOptions{
|
|
RelativeBase: base,
|
|
SkipSchemas: false,
|
|
ContinueOnError: false,
|
|
}
|
|
return ExpandSchemaWithBasePath(schema, cache, opts)
|
|
}
|
|
|
|
// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options
|
|
func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {
|
|
if schema == nil {
|
|
return nil
|
|
}
|
|
|
|
var basePath string
|
|
if opts.RelativeBase != "" {
|
|
basePath, _ = absPath(opts.RelativeBase)
|
|
}
|
|
|
|
resolver, err := defaultSchemaLoader(nil, opts, cache)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
refs := []string{""}
|
|
var s *Schema
|
|
if s, err = expandSchema(*schema, refs, resolver, basePath); err != nil {
|
|
return err
|
|
}
|
|
*schema = *s
|
|
return nil
|
|
}
|
|
|
|
func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
|
if target.Items != nil {
|
|
if target.Items.Schema != nil {
|
|
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
*target.Items.Schema = *t
|
|
}
|
|
for i := range target.Items.Schemas {
|
|
t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
target.Items.Schemas[i] = *t
|
|
}
|
|
}
|
|
return &target, nil
|
|
}
|
|
|
|
// basePathFromSchemaID returns a new basePath based on an existing basePath and a schema ID
|
|
func basePathFromSchemaID(oldBasePath, id string) string {
|
|
u, err := url.Parse(oldBasePath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
uid, err := url.Parse(id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if path.IsAbs(uid.Path) {
|
|
return id
|
|
}
|
|
u.Path = path.Join(path.Dir(u.Path), uid.Path)
|
|
return u.String()
|
|
}
|
|
|
|
func isCircular(ref *Ref, basePath string, parentRefs ...string) bool {
|
|
return basePath != "" && swag.ContainsStringsCI(parentRefs, ref.String())
|
|
}
|
|
|
|
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
|
if target.Ref.String() == "" && target.Ref.IsRoot() {
|
|
// normalizing is important
|
|
newRef := normalizeFileRef(&target.Ref, basePath)
|
|
target.Ref = *newRef
|
|
return &target, nil
|
|
|
|
}
|
|
|
|
/* change the base path of resolution when an ID is encountered
|
|
otherwise the basePath should inherit the parent's */
|
|
// important: ID can be relative path
|
|
if target.ID != "" {
|
|
// handling the case when id is a folder
|
|
// remember that basePath has to be a file
|
|
refPath := target.ID
|
|
if strings.HasSuffix(target.ID, "/") {
|
|
// path.Clean here would not work correctly if basepath is http
|
|
refPath = fmt.Sprintf("%s%s", refPath, "placeholder.json")
|
|
}
|
|
basePath = normalizePaths(refPath, basePath)
|
|
}
|
|
|
|
/* Explain here what this function does */
|
|
|
|
var t *Schema
|
|
/* if Ref is found, everything else doesn't matter */
|
|
/* Ref also changes the resolution scope of children expandSchema */
|
|
if target.Ref.String() != "" {
|
|
/* Here the resolution scope is changed because a $ref was encountered */
|
|
normalizedRef := normalizeFileRef(&target.Ref, basePath)
|
|
normalizedBasePath := normalizedRef.RemoteURI()
|
|
|
|
/* this means there is a circle in the recursion tree */
|
|
/* return the Ref */
|
|
if isCircular(normalizedRef, basePath, parentRefs...) {
|
|
target.Ref = *normalizedRef
|
|
return &target, nil
|
|
}
|
|
|
|
debugLog("\nbasePath: %s", basePath)
|
|
if Debug {
|
|
b, _ := json.Marshal(target)
|
|
debugLog("calling Resolve with target: %s", string(b))
|
|
}
|
|
if err := resolver.Resolve(&target.Ref, &t, basePath); shouldStopOnError(err, resolver.options) {
|
|
return nil, err
|
|
}
|
|
|
|
if t != nil {
|
|
parentRefs = append(parentRefs, normalizedRef.String())
|
|
var err error
|
|
resolver, err = transitiveResolver(basePath, target.Ref, resolver)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return nil, err
|
|
}
|
|
|
|
return expandSchema(*t, parentRefs, resolver, normalizedBasePath)
|
|
}
|
|
}
|
|
|
|
t, err := expandItems(target, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
target = *t
|
|
}
|
|
|
|
for i := range target.AllOf {
|
|
t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
target.AllOf[i] = *t
|
|
}
|
|
for i := range target.AnyOf {
|
|
t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
target.AnyOf[i] = *t
|
|
}
|
|
for i := range target.OneOf {
|
|
t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
target.OneOf[i] = *t
|
|
}
|
|
}
|
|
if target.Not != nil {
|
|
t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
*target.Not = *t
|
|
}
|
|
}
|
|
for k := range target.Properties {
|
|
t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
target.Properties[k] = *t
|
|
}
|
|
}
|
|
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
|
|
t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
*target.AdditionalProperties.Schema = *t
|
|
}
|
|
}
|
|
for k := range target.PatternProperties {
|
|
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
target.PatternProperties[k] = *t
|
|
}
|
|
}
|
|
for k := range target.Dependencies {
|
|
if target.Dependencies[k].Schema != nil {
|
|
t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
*target.Dependencies[k].Schema = *t
|
|
}
|
|
}
|
|
}
|
|
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
|
|
t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
*target.AdditionalItems.Schema = *t
|
|
}
|
|
}
|
|
for k := range target.Definitions {
|
|
t, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return &target, err
|
|
}
|
|
if t != nil {
|
|
target.Definitions[k] = *t
|
|
}
|
|
}
|
|
return &target, nil
|
|
}
|
|
|
|
func derefPathItem(pathItem *PathItem, parentRefs []string, resolver *schemaLoader, basePath string) error {
|
|
curRef := pathItem.Ref.String()
|
|
if curRef != "" {
|
|
normalizedRef := normalizeFileRef(&pathItem.Ref, basePath)
|
|
normalizedBasePath := normalizedRef.RemoteURI()
|
|
|
|
if isCircular(normalizedRef, basePath, parentRefs...) {
|
|
return nil
|
|
}
|
|
|
|
if err := resolver.Resolve(&pathItem.Ref, pathItem, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
|
|
if pathItem.Ref.String() != "" && pathItem.Ref.String() != curRef && basePath != normalizedBasePath {
|
|
parentRefs = append(parentRefs, normalizedRef.String())
|
|
return derefPathItem(pathItem, parentRefs, resolver, normalizedBasePath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
|
|
if pathItem == nil {
|
|
return nil
|
|
}
|
|
|
|
parentRefs := []string{}
|
|
if err := derefPathItem(pathItem, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if pathItem.Ref.String() != "" {
|
|
var err error
|
|
resolver, err = transitiveResolver(basePath, pathItem.Ref, resolver)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
}
|
|
pathItem.Ref = Ref{}
|
|
|
|
parentRefs = parentRefs[0:]
|
|
|
|
for idx := range pathItem.Parameters {
|
|
if err := expandParameter(&(pathItem.Parameters[idx]), resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
}
|
|
if err := expandOperation(pathItem.Get, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Head, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Options, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Put, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Post, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Patch, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if err := expandOperation(pathItem.Delete, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
|
|
if op == nil {
|
|
return nil
|
|
}
|
|
|
|
for i, param := range op.Parameters {
|
|
if err := expandParameter(¶m, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
op.Parameters[i] = param
|
|
}
|
|
|
|
if op.Responses != nil {
|
|
responses := op.Responses
|
|
if err := expandResponse(responses.Default, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
for code, response := range responses.StatusCodeResponses {
|
|
if err := expandResponse(&response, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
responses.StatusCodeResponses[code] = response
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func transitiveResolver(basePath string, ref Ref, resolver *schemaLoader) (*schemaLoader, error) {
|
|
if ref.IsRoot() || ref.HasFragmentOnly {
|
|
return resolver, nil
|
|
}
|
|
|
|
baseRef, _ := NewRef(basePath)
|
|
currentRef := normalizeFileRef(&ref, basePath)
|
|
// Set a new root to resolve against
|
|
if !strings.HasPrefix(currentRef.String(), baseRef.String()) {
|
|
rootURL := currentRef.GetURL()
|
|
rootURL.Fragment = ""
|
|
root, _ := resolver.cache.Get(rootURL.String())
|
|
var err error
|
|
resolver, err = defaultSchemaLoader(root, resolver.options, resolver.cache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return resolver, nil
|
|
}
|
|
|
|
// ExpandResponse expands a response based on a basepath
|
|
// This is the exported version of expandResponse
|
|
// all refs inside response will be resolved relative to basePath
|
|
func ExpandResponse(response *Response, basePath string) error {
|
|
opts := &ExpandOptions{
|
|
RelativeBase: basePath,
|
|
}
|
|
resolver, err := defaultSchemaLoader(nil, opts, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expandResponse(response, resolver, basePath)
|
|
}
|
|
|
|
func derefResponse(response *Response, parentRefs []string, resolver *schemaLoader, basePath string) error {
|
|
curRef := response.Ref.String()
|
|
if curRef != "" {
|
|
/* Here the resolution scope is changed because a $ref was encountered */
|
|
normalizedRef := normalizeFileRef(&response.Ref, basePath)
|
|
normalizedBasePath := normalizedRef.RemoteURI()
|
|
|
|
if isCircular(normalizedRef, basePath, parentRefs...) {
|
|
return nil
|
|
}
|
|
|
|
if err := resolver.Resolve(&response.Ref, response, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
|
|
if response.Ref.String() != "" && response.Ref.String() != curRef && basePath != normalizedBasePath {
|
|
parentRefs = append(parentRefs, normalizedRef.String())
|
|
return derefResponse(response, parentRefs, resolver, normalizedBasePath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func expandResponse(response *Response, resolver *schemaLoader, basePath string) error {
|
|
if response == nil {
|
|
return nil
|
|
}
|
|
|
|
parentRefs := []string{}
|
|
if err := derefResponse(response, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if response.Ref.String() != "" {
|
|
var err error
|
|
resolver, err = transitiveResolver(basePath, response.Ref, resolver)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
}
|
|
response.Ref = Ref{}
|
|
|
|
parentRefs = parentRefs[0:]
|
|
if !resolver.options.SkipSchemas && response.Schema != nil {
|
|
parentRefs = append(parentRefs, response.Schema.Ref.String())
|
|
s, err := expandSchema(*response.Schema, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
*response.Schema = *s
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExpandParameter expands a parameter based on a basepath
|
|
// This is the exported version of expandParameter
|
|
// all refs inside parameter will be resolved relative to basePath
|
|
func ExpandParameter(parameter *Parameter, basePath string) error {
|
|
opts := &ExpandOptions{
|
|
RelativeBase: basePath,
|
|
}
|
|
resolver, err := defaultSchemaLoader(nil, opts, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expandParameter(parameter, resolver, basePath)
|
|
}
|
|
|
|
func derefParameter(parameter *Parameter, parentRefs []string, resolver *schemaLoader, basePath string) error {
|
|
curRef := parameter.Ref.String()
|
|
if curRef != "" {
|
|
normalizedRef := normalizeFileRef(¶meter.Ref, basePath)
|
|
normalizedBasePath := normalizedRef.RemoteURI()
|
|
|
|
if isCircular(normalizedRef, basePath, parentRefs...) {
|
|
return nil
|
|
}
|
|
|
|
if err := resolver.Resolve(¶meter.Ref, parameter, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
|
|
if parameter.Ref.String() != "" && parameter.Ref.String() != curRef && basePath != normalizedBasePath {
|
|
parentRefs = append(parentRefs, normalizedRef.String())
|
|
return derefParameter(parameter, parentRefs, resolver, normalizedBasePath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func expandParameter(parameter *Parameter, resolver *schemaLoader, basePath string) error {
|
|
if parameter == nil {
|
|
return nil
|
|
}
|
|
|
|
parentRefs := []string{}
|
|
if err := derefParameter(parameter, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
if parameter.Ref.String() != "" {
|
|
var err error
|
|
resolver, err = transitiveResolver(basePath, parameter.Ref, resolver)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
}
|
|
parameter.Ref = Ref{}
|
|
|
|
parentRefs = parentRefs[0:]
|
|
if !resolver.options.SkipSchemas && parameter.Schema != nil {
|
|
parentRefs = append(parentRefs, parameter.Schema.Ref.String())
|
|
s, err := expandSchema(*parameter.Schema, parentRefs, resolver, basePath)
|
|
if shouldStopOnError(err, resolver.options) {
|
|
return err
|
|
}
|
|
*parameter.Schema = *s
|
|
}
|
|
return nil
|
|
}
|