...

Source file src/github.com/go-openapi/analysis/schema.go

Documentation: github.com/go-openapi/analysis

     1  package analysis
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/go-openapi/spec"
     7  	"github.com/go-openapi/strfmt"
     8  )
     9  
    10  // SchemaOpts configures the schema analyzer
    11  type SchemaOpts struct {
    12  	Schema   *spec.Schema
    13  	Root     interface{}
    14  	BasePath string
    15  	_        struct{}
    16  }
    17  
    18  // Schema analysis, will classify the schema according to known
    19  // patterns.
    20  func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
    21  	if opts.Schema == nil {
    22  		return nil, errors.New("no schema to analyze")
    23  	}
    24  
    25  	a := &AnalyzedSchema{
    26  		schema:   opts.Schema,
    27  		root:     opts.Root,
    28  		basePath: opts.BasePath,
    29  	}
    30  
    31  	a.initializeFlags()
    32  	a.inferKnownType()
    33  	a.inferEnum()
    34  	a.inferBaseType()
    35  
    36  	if err := a.inferMap(); err != nil {
    37  		return nil, err
    38  	}
    39  	if err := a.inferArray(); err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	a.inferTuple()
    44  
    45  	if err := a.inferFromRef(); err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	a.inferSimpleSchema()
    50  
    51  	return a, nil
    52  }
    53  
    54  // AnalyzedSchema indicates what the schema represents
    55  type AnalyzedSchema struct {
    56  	schema   *spec.Schema
    57  	root     interface{}
    58  	basePath string
    59  
    60  	hasProps           bool
    61  	hasAllOf           bool
    62  	hasItems           bool
    63  	hasAdditionalProps bool
    64  	hasAdditionalItems bool
    65  	hasRef             bool
    66  
    67  	IsKnownType      bool
    68  	IsSimpleSchema   bool
    69  	IsArray          bool
    70  	IsSimpleArray    bool
    71  	IsMap            bool
    72  	IsSimpleMap      bool
    73  	IsExtendedObject bool
    74  	IsTuple          bool
    75  	IsTupleWithExtra bool
    76  	IsBaseType       bool
    77  	IsEnum           bool
    78  }
    79  
    80  // Inherits copies value fields from other onto this schema
    81  func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) {
    82  	if other == nil {
    83  		return
    84  	}
    85  	a.hasProps = other.hasProps
    86  	a.hasAllOf = other.hasAllOf
    87  	a.hasItems = other.hasItems
    88  	a.hasAdditionalItems = other.hasAdditionalItems
    89  	a.hasAdditionalProps = other.hasAdditionalProps
    90  	a.hasRef = other.hasRef
    91  
    92  	a.IsKnownType = other.IsKnownType
    93  	a.IsSimpleSchema = other.IsSimpleSchema
    94  	a.IsArray = other.IsArray
    95  	a.IsSimpleArray = other.IsSimpleArray
    96  	a.IsMap = other.IsMap
    97  	a.IsSimpleMap = other.IsSimpleMap
    98  	a.IsExtendedObject = other.IsExtendedObject
    99  	a.IsTuple = other.IsTuple
   100  	a.IsTupleWithExtra = other.IsTupleWithExtra
   101  	a.IsBaseType = other.IsBaseType
   102  	a.IsEnum = other.IsEnum
   103  }
   104  
   105  func (a *AnalyzedSchema) inferFromRef() error {
   106  	if a.hasRef {
   107  		sch := new(spec.Schema)
   108  		sch.Ref = a.schema.Ref
   109  		err := spec.ExpandSchema(sch, a.root, nil)
   110  		if err != nil {
   111  			return err
   112  		}
   113  		rsch, err := Schema(SchemaOpts{
   114  			Schema:   sch,
   115  			Root:     a.root,
   116  			BasePath: a.basePath,
   117  		})
   118  		if err != nil {
   119  			// NOTE(fredbi): currently the only cause for errors is
   120  			// unresolved ref. Since spec.ExpandSchema() expands the
   121  			// schema recursively, there is no chance to get there,
   122  			// until we add more causes for error in this schema analysis.
   123  			return err
   124  		}
   125  		a.inherits(rsch)
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (a *AnalyzedSchema) inferSimpleSchema() {
   132  	a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap
   133  }
   134  
   135  func (a *AnalyzedSchema) inferKnownType() {
   136  	tpe := a.schema.Type
   137  	format := a.schema.Format
   138  	a.IsKnownType = tpe.Contains("boolean") ||
   139  		tpe.Contains("integer") ||
   140  		tpe.Contains("number") ||
   141  		tpe.Contains("string") ||
   142  		(format != "" && strfmt.Default.ContainsName(format)) ||
   143  		(a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems)
   144  }
   145  
   146  func (a *AnalyzedSchema) inferMap() error {
   147  	if !a.isObjectType() {
   148  		return nil
   149  	}
   150  
   151  	hasExtra := a.hasProps || a.hasAllOf
   152  	a.IsMap = a.hasAdditionalProps && !hasExtra
   153  	a.IsExtendedObject = a.hasAdditionalProps && hasExtra
   154  
   155  	if !a.IsMap {
   156  		return nil
   157  	}
   158  
   159  	// maps
   160  	if a.schema.AdditionalProperties.Schema != nil {
   161  		msch, err := Schema(SchemaOpts{
   162  			Schema:   a.schema.AdditionalProperties.Schema,
   163  			Root:     a.root,
   164  			BasePath: a.basePath,
   165  		})
   166  		if err != nil {
   167  			return err
   168  		}
   169  		a.IsSimpleMap = msch.IsSimpleSchema
   170  	} else if a.schema.AdditionalProperties.Allows {
   171  		a.IsSimpleMap = true
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func (a *AnalyzedSchema) inferArray() error {
   178  	// an array has Items defined as an object schema, otherwise we qualify this JSON array as a tuple
   179  	// (yes, even if the Items array contains only one element).
   180  	// arrays in JSON schema may be unrestricted (i.e no Items specified).
   181  	// Note that arrays in Swagger MUST have Items. Nonetheless, we analyze unrestricted arrays.
   182  	//
   183  	// NOTE: the spec package misses the distinction between:
   184  	// items: [] and items: {}, so we consider both arrays here.
   185  	a.IsArray = a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Schemas == nil)
   186  	if a.IsArray && a.hasItems {
   187  		if a.schema.Items.Schema != nil {
   188  			itsch, err := Schema(SchemaOpts{
   189  				Schema:   a.schema.Items.Schema,
   190  				Root:     a.root,
   191  				BasePath: a.basePath,
   192  			})
   193  			if err != nil {
   194  				return err
   195  			}
   196  
   197  			a.IsSimpleArray = itsch.IsSimpleSchema
   198  		}
   199  	}
   200  
   201  	if a.IsArray && !a.hasItems {
   202  		a.IsSimpleArray = true
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  func (a *AnalyzedSchema) inferTuple() {
   209  	tuple := a.hasItems && a.schema.Items.Schemas != nil
   210  	a.IsTuple = tuple && !a.hasAdditionalItems
   211  	a.IsTupleWithExtra = tuple && a.hasAdditionalItems
   212  }
   213  
   214  func (a *AnalyzedSchema) inferBaseType() {
   215  	if a.isObjectType() {
   216  		a.IsBaseType = a.schema.Discriminator != ""
   217  	}
   218  }
   219  
   220  func (a *AnalyzedSchema) inferEnum() {
   221  	a.IsEnum = len(a.schema.Enum) > 0
   222  }
   223  
   224  func (a *AnalyzedSchema) initializeFlags() {
   225  	a.hasProps = len(a.schema.Properties) > 0
   226  	a.hasAllOf = len(a.schema.AllOf) > 0
   227  	a.hasRef = a.schema.Ref.String() != ""
   228  
   229  	a.hasItems = a.schema.Items != nil &&
   230  		(a.schema.Items.Schema != nil || len(a.schema.Items.Schemas) > 0)
   231  
   232  	a.hasAdditionalProps = a.schema.AdditionalProperties != nil &&
   233  		(a.schema.AdditionalProperties.Schema != nil || a.schema.AdditionalProperties.Allows)
   234  
   235  	a.hasAdditionalItems = a.schema.AdditionalItems != nil &&
   236  		(a.schema.AdditionalItems.Schema != nil || a.schema.AdditionalItems.Allows)
   237  }
   238  
   239  func (a *AnalyzedSchema) isObjectType() bool {
   240  	return !a.hasRef && (a.schema.Type == nil || a.schema.Type.Contains("") || a.schema.Type.Contains("object"))
   241  }
   242  
   243  func (a *AnalyzedSchema) isArrayType() bool {
   244  	return !a.hasRef && (a.schema.Type != nil && a.schema.Type.Contains("array"))
   245  }
   246  
   247  // isAnalyzedAsComplex determines if an analyzed schema is eligible to flattening (i.e. it is "complex").
   248  //
   249  // Complex means the schema is any of:
   250  //   - a simple type (primitive)
   251  //   - an array of something (items are possibly complex ; if this is the case, items will generate a definition)
   252  //   - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will
   253  //     generate a definition)
   254  func (a *AnalyzedSchema) isAnalyzedAsComplex() bool {
   255  	return !a.IsSimpleSchema && !a.IsArray && !a.IsMap
   256  }
   257  

View as plain text