...

Source file src/github.com/go-openapi/analysis/internal/flatten/replace/replace.go

Documentation: github.com/go-openapi/analysis/internal/flatten/replace

     1  package replace
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/url"
     7  	"os"
     8  	"path"
     9  	"strconv"
    10  
    11  	"github.com/go-openapi/analysis/internal/debug"
    12  	"github.com/go-openapi/jsonpointer"
    13  	"github.com/go-openapi/spec"
    14  )
    15  
    16  const definitionsPath = "#/definitions"
    17  
    18  var debugLog = debug.GetLogger("analysis/flatten/replace", os.Getenv("SWAGGER_DEBUG") != "")
    19  
    20  // RewriteSchemaToRef replaces a schema with a Ref
    21  func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
    22  	debugLog("rewriting schema to ref for %s with %s", key, ref.String())
    23  	_, value, err := getPointerFromKey(sp, key)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	switch refable := value.(type) {
    29  	case *spec.Schema:
    30  		return rewriteParentRef(sp, key, ref)
    31  
    32  	case spec.Schema:
    33  		return rewriteParentRef(sp, key, ref)
    34  
    35  	case *spec.SchemaOrArray:
    36  		if refable.Schema != nil {
    37  			refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    38  		}
    39  
    40  	case *spec.SchemaOrBool:
    41  		if refable.Schema != nil {
    42  			refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    43  		}
    44  	case map[string]interface{}: // this happens e.g. if a schema points to an extension unmarshaled as map[string]interface{}
    45  		return rewriteParentRef(sp, key, ref)
    46  	default:
    47  		return fmt.Errorf("no schema with ref found at %s for %T", key, value)
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
    54  	parent, entry, pvalue, err := getParentFromKey(sp, key)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	debugLog("rewriting holder for %T", pvalue)
    60  	switch container := pvalue.(type) {
    61  	case spec.Response:
    62  		if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
    63  			return err
    64  		}
    65  
    66  	case *spec.Response:
    67  		container.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    68  
    69  	case *spec.Responses:
    70  		statusCode, err := strconv.Atoi(entry)
    71  		if err != nil {
    72  			return fmt.Errorf("%s not a number: %w", key[1:], err)
    73  		}
    74  		resp := container.StatusCodeResponses[statusCode]
    75  		resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    76  		container.StatusCodeResponses[statusCode] = resp
    77  
    78  	case map[string]spec.Response:
    79  		resp := container[entry]
    80  		resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    81  		container[entry] = resp
    82  
    83  	case spec.Parameter:
    84  		if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
    85  			return err
    86  		}
    87  
    88  	case map[string]spec.Parameter:
    89  		param := container[entry]
    90  		param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
    91  		container[entry] = param
    92  
    93  	case []spec.Parameter:
    94  		idx, err := strconv.Atoi(entry)
    95  		if err != nil {
    96  			return fmt.Errorf("%s not a number: %w", key[1:], err)
    97  		}
    98  		param := container[idx]
    99  		param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   100  		container[idx] = param
   101  
   102  	case spec.Definitions:
   103  		container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   104  
   105  	case map[string]spec.Schema:
   106  		container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   107  
   108  	case []spec.Schema:
   109  		idx, err := strconv.Atoi(entry)
   110  		if err != nil {
   111  			return fmt.Errorf("%s not a number: %w", key[1:], err)
   112  		}
   113  		container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   114  
   115  	case *spec.SchemaOrArray:
   116  		// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
   117  		idx, err := strconv.Atoi(entry)
   118  		if err != nil {
   119  			return fmt.Errorf("%s not a number: %w", key[1:], err)
   120  		}
   121  		container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   122  
   123  	case spec.SchemaProperties:
   124  		container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   125  
   126  	case *interface{}:
   127  		*container = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   128  
   129  	// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
   130  
   131  	default:
   132  		return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  // getPointerFromKey retrieves the content of the JSON pointer "key"
   139  func getPointerFromKey(sp interface{}, key string) (string, interface{}, error) {
   140  	switch sp.(type) {
   141  	case *spec.Schema:
   142  	case *spec.Swagger:
   143  	default:
   144  		panic("unexpected type used in getPointerFromKey")
   145  	}
   146  	if key == "#/" {
   147  		return "", sp, nil
   148  	}
   149  	// unescape chars in key, e.g. "{}" from path params
   150  	pth, _ := url.PathUnescape(key[1:])
   151  	ptr, err := jsonpointer.New(pth)
   152  	if err != nil {
   153  		return "", nil, err
   154  	}
   155  
   156  	value, _, err := ptr.Get(sp)
   157  	if err != nil {
   158  		debugLog("error when getting key: %s with path: %s", key, pth)
   159  
   160  		return "", nil, err
   161  	}
   162  
   163  	return pth, value, nil
   164  }
   165  
   166  // getParentFromKey retrieves the container of the JSON pointer "key"
   167  func getParentFromKey(sp interface{}, key string) (string, string, interface{}, error) {
   168  	switch sp.(type) {
   169  	case *spec.Schema:
   170  	case *spec.Swagger:
   171  	default:
   172  		panic("unexpected type used in getPointerFromKey")
   173  	}
   174  	// unescape chars in key, e.g. "{}" from path params
   175  	pth, _ := url.PathUnescape(key[1:])
   176  
   177  	parent, entry := path.Dir(pth), path.Base(pth)
   178  	debugLog("getting schema holder at: %s, with entry: %s", parent, entry)
   179  
   180  	pptr, err := jsonpointer.New(parent)
   181  	if err != nil {
   182  		return "", "", nil, err
   183  	}
   184  	pvalue, _, err := pptr.Get(sp)
   185  	if err != nil {
   186  		return "", "", nil, fmt.Errorf("can't get parent for %s: %w", parent, err)
   187  	}
   188  
   189  	return parent, entry, pvalue, nil
   190  }
   191  
   192  // UpdateRef replaces a ref by another one
   193  func UpdateRef(sp interface{}, key string, ref spec.Ref) error {
   194  	switch sp.(type) {
   195  	case *spec.Schema:
   196  	case *spec.Swagger:
   197  	default:
   198  		panic("unexpected type used in getPointerFromKey")
   199  	}
   200  	debugLog("updating ref for %s with %s", key, ref.String())
   201  	pth, value, err := getPointerFromKey(sp, key)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	switch refable := value.(type) {
   207  	case *spec.Schema:
   208  		refable.Ref = ref
   209  	case *spec.SchemaOrArray:
   210  		if refable.Schema != nil {
   211  			refable.Schema.Ref = ref
   212  		}
   213  	case *spec.SchemaOrBool:
   214  		if refable.Schema != nil {
   215  			refable.Schema.Ref = ref
   216  		}
   217  	case spec.Schema:
   218  		debugLog("rewriting holder for %T", refable)
   219  		_, entry, pvalue, erp := getParentFromKey(sp, key)
   220  		if erp != nil {
   221  			return err
   222  		}
   223  		switch container := pvalue.(type) {
   224  		case spec.Definitions:
   225  			container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   226  
   227  		case map[string]spec.Schema:
   228  			container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   229  
   230  		case []spec.Schema:
   231  			idx, err := strconv.Atoi(entry)
   232  			if err != nil {
   233  				return fmt.Errorf("%s not a number: %w", pth, err)
   234  			}
   235  			container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   236  
   237  		case *spec.SchemaOrArray:
   238  			// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
   239  			idx, err := strconv.Atoi(entry)
   240  			if err != nil {
   241  				return fmt.Errorf("%s not a number: %w", pth, err)
   242  			}
   243  			container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   244  
   245  		case spec.SchemaProperties:
   246  			container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
   247  
   248  		// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
   249  
   250  		default:
   251  			return fmt.Errorf("unhandled container type at %s: %T", key, value)
   252  		}
   253  
   254  	default:
   255  		return fmt.Errorf("no schema with ref found at %s for %T", key, value)
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  // UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema)
   262  func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
   263  	debugLog("updating ref for %s with schema", key)
   264  	pth, value, err := getPointerFromKey(sp, key)
   265  	if err != nil {
   266  		return err
   267  	}
   268  
   269  	switch refable := value.(type) {
   270  	case *spec.Schema:
   271  		*refable = *sch
   272  	case spec.Schema:
   273  		_, entry, pvalue, erp := getParentFromKey(sp, key)
   274  		if erp != nil {
   275  			return err
   276  		}
   277  		switch container := pvalue.(type) {
   278  		case spec.Definitions:
   279  			container[entry] = *sch
   280  
   281  		case map[string]spec.Schema:
   282  			container[entry] = *sch
   283  
   284  		case []spec.Schema:
   285  			idx, err := strconv.Atoi(entry)
   286  			if err != nil {
   287  				return fmt.Errorf("%s not a number: %w", pth, err)
   288  			}
   289  			container[idx] = *sch
   290  
   291  		case *spec.SchemaOrArray:
   292  			// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
   293  			idx, err := strconv.Atoi(entry)
   294  			if err != nil {
   295  				return fmt.Errorf("%s not a number: %w", pth, err)
   296  			}
   297  			container.Schemas[idx] = *sch
   298  
   299  		case spec.SchemaProperties:
   300  			container[entry] = *sch
   301  
   302  		// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
   303  
   304  		default:
   305  			return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value)
   306  		}
   307  	case *spec.SchemaOrArray:
   308  		*refable.Schema = *sch
   309  	// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
   310  	case *spec.SchemaOrBool:
   311  		*refable.Schema = *sch
   312  	default:
   313  		return fmt.Errorf("no schema with ref found at %s for %T", key, value)
   314  	}
   315  
   316  	return nil
   317  }
   318  
   319  // DeepestRefResult holds the results from DeepestRef analysis
   320  type DeepestRefResult struct {
   321  	Ref      spec.Ref
   322  	Schema   *spec.Schema
   323  	Warnings []string
   324  }
   325  
   326  // DeepestRef finds the first definition ref, from a cascade of nested refs which are not definitions.
   327  //   - if no definition is found, returns the deepest ref.
   328  //   - pointers to external files are expanded
   329  //
   330  // NOTE: all external $ref's are assumed to be already expanded at this stage.
   331  func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) {
   332  	if !ref.HasFragmentOnly {
   333  		// we found an external $ref, which is odd at this stage:
   334  		// do nothing on external $refs
   335  		return &DeepestRefResult{Ref: ref}, nil
   336  	}
   337  
   338  	currentRef := ref
   339  	visited := make(map[string]bool, 64)
   340  	warnings := make([]string, 0, 2)
   341  
   342  DOWNREF:
   343  	for currentRef.String() != "" {
   344  		if path.Dir(currentRef.String()) == definitionsPath {
   345  			// this is a top-level definition: stop here and return this ref
   346  			return &DeepestRefResult{Ref: currentRef}, nil
   347  		}
   348  
   349  		if _, beenThere := visited[currentRef.String()]; beenThere {
   350  			return nil,
   351  				fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String())
   352  		}
   353  
   354  		visited[currentRef.String()] = true
   355  		value, _, err := currentRef.GetPointer().Get(sp)
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  
   360  		switch refable := value.(type) {
   361  		case *spec.Schema:
   362  			if refable.Ref.String() == "" {
   363  				break DOWNREF
   364  			}
   365  			currentRef = refable.Ref
   366  
   367  		case spec.Schema:
   368  			if refable.Ref.String() == "" {
   369  				break DOWNREF
   370  			}
   371  			currentRef = refable.Ref
   372  
   373  		case *spec.SchemaOrArray:
   374  			if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
   375  				break DOWNREF
   376  			}
   377  			currentRef = refable.Schema.Ref
   378  
   379  		case *spec.SchemaOrBool:
   380  			if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
   381  				break DOWNREF
   382  			}
   383  			currentRef = refable.Schema.Ref
   384  
   385  		case spec.Response:
   386  			// a pointer points to a schema initially marshalled in responses section...
   387  			// Attempt to convert this to a schema. If this fails, the spec is invalid
   388  			asJSON, _ := refable.MarshalJSON()
   389  			var asSchema spec.Schema
   390  
   391  			err := asSchema.UnmarshalJSON(asJSON)
   392  			if err != nil {
   393  				return nil,
   394  					fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
   395  						currentRef.String(), value, err,
   396  					)
   397  			}
   398  			warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
   399  
   400  			if asSchema.Ref.String() == "" {
   401  				break DOWNREF
   402  			}
   403  			currentRef = asSchema.Ref
   404  
   405  		case spec.Parameter:
   406  			// a pointer points to a schema initially marshalled in parameters section...
   407  			// Attempt to convert this to a schema. If this fails, the spec is invalid
   408  			asJSON, _ := refable.MarshalJSON()
   409  			var asSchema spec.Schema
   410  			if err := asSchema.UnmarshalJSON(asJSON); err != nil {
   411  				return nil,
   412  					fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
   413  						currentRef.String(), value, err,
   414  					)
   415  			}
   416  
   417  			warnings = append(warnings, fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
   418  
   419  			if asSchema.Ref.String() == "" {
   420  				break DOWNREF
   421  			}
   422  			currentRef = asSchema.Ref
   423  
   424  		default:
   425  			// fallback: attempts to resolve the pointer as a schema
   426  			if refable == nil {
   427  				break DOWNREF
   428  			}
   429  
   430  			asJSON, _ := json.Marshal(refable)
   431  			var asSchema spec.Schema
   432  			if err := asSchema.UnmarshalJSON(asJSON); err != nil {
   433  				return nil,
   434  					fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T (%v)",
   435  						currentRef.String(), value, err,
   436  					)
   437  			}
   438  			warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable))
   439  
   440  			if asSchema.Ref.String() == "" {
   441  				break DOWNREF
   442  			}
   443  			currentRef = asSchema.Ref
   444  		}
   445  	}
   446  
   447  	// assess what schema we're ending with
   448  	sch, erv := spec.ResolveRefWithBase(sp, &currentRef, opts)
   449  	if erv != nil {
   450  		return nil, erv
   451  	}
   452  
   453  	if sch == nil {
   454  		return nil, fmt.Errorf("no schema found at %s", currentRef.String())
   455  	}
   456  
   457  	return &DeepestRefResult{Ref: currentRef, Schema: sch, Warnings: warnings}, nil
   458  }
   459  

View as plain text