| package analysis |
| |
| import ( |
| "github.com/go-openapi/spec" |
| "github.com/go-openapi/strfmt" |
| ) |
| |
| // SchemaOpts configures the schema analyzer |
| type SchemaOpts struct { |
| Schema *spec.Schema |
| Root interface{} |
| BasePath string |
| _ struct{} |
| } |
| |
| // Schema analysis, will classify the schema according to known |
| // patterns. |
| func Schema(opts SchemaOpts) (*AnalyzedSchema, error) { |
| a := &AnalyzedSchema{ |
| schema: opts.Schema, |
| root: opts.Root, |
| basePath: opts.BasePath, |
| } |
| |
| a.initializeFlags() |
| a.inferKnownType() |
| a.inferEnum() |
| a.inferBaseType() |
| |
| if err := a.inferMap(); err != nil { |
| return nil, err |
| } |
| if err := a.inferArray(); err != nil { |
| return nil, err |
| } |
| |
| if err := a.inferTuple(); err != nil { |
| // NOTE(fredbi): currently, inferTuple() never returns an error |
| return nil, err |
| } |
| |
| if err := a.inferFromRef(); err != nil { |
| return nil, err |
| } |
| |
| a.inferSimpleSchema() |
| return a, nil |
| } |
| |
| // AnalyzedSchema indicates what the schema represents |
| type AnalyzedSchema struct { |
| schema *spec.Schema |
| root interface{} |
| basePath string |
| |
| hasProps bool |
| hasAllOf bool |
| hasItems bool |
| hasAdditionalProps bool |
| hasAdditionalItems bool |
| hasRef bool |
| |
| IsKnownType bool |
| IsSimpleSchema bool |
| IsArray bool |
| IsSimpleArray bool |
| IsMap bool |
| IsSimpleMap bool |
| IsExtendedObject bool |
| IsTuple bool |
| IsTupleWithExtra bool |
| IsBaseType bool |
| IsEnum bool |
| } |
| |
| // Inherits copies value fields from other onto this schema |
| func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) { |
| if other == nil { |
| return |
| } |
| a.hasProps = other.hasProps |
| a.hasAllOf = other.hasAllOf |
| a.hasItems = other.hasItems |
| a.hasAdditionalItems = other.hasAdditionalItems |
| a.hasAdditionalProps = other.hasAdditionalProps |
| a.hasRef = other.hasRef |
| |
| a.IsKnownType = other.IsKnownType |
| a.IsSimpleSchema = other.IsSimpleSchema |
| a.IsArray = other.IsArray |
| a.IsSimpleArray = other.IsSimpleArray |
| a.IsMap = other.IsMap |
| a.IsSimpleMap = other.IsSimpleMap |
| a.IsExtendedObject = other.IsExtendedObject |
| a.IsTuple = other.IsTuple |
| a.IsTupleWithExtra = other.IsTupleWithExtra |
| a.IsBaseType = other.IsBaseType |
| a.IsEnum = other.IsEnum |
| } |
| |
| func (a *AnalyzedSchema) inferFromRef() error { |
| if a.hasRef { |
| sch := new(spec.Schema) |
| sch.Ref = a.schema.Ref |
| err := spec.ExpandSchema(sch, a.root, nil) |
| if err != nil { |
| return err |
| } |
| if sch != nil { |
| // NOTE(fredbi): currently the only cause for errors in |
| // unresolved ref. Since spec.ExpandSchema() expands the |
| // schema recursively, there is no chance to get there, |
| // until we add more causes for error in this schema analysis. |
| rsch, err := Schema(SchemaOpts{ |
| Schema: sch, |
| Root: a.root, |
| BasePath: a.basePath, |
| }) |
| if err != nil { |
| return err |
| } |
| a.inherits(rsch) |
| } |
| } |
| return nil |
| } |
| |
| func (a *AnalyzedSchema) inferSimpleSchema() { |
| a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap |
| } |
| |
| func (a *AnalyzedSchema) inferKnownType() { |
| tpe := a.schema.Type |
| format := a.schema.Format |
| a.IsKnownType = tpe.Contains("boolean") || |
| tpe.Contains("integer") || |
| tpe.Contains("number") || |
| tpe.Contains("string") || |
| (format != "" && strfmt.Default.ContainsName(format)) || |
| (a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems) |
| } |
| |
| func (a *AnalyzedSchema) inferMap() error { |
| if a.isObjectType() { |
| hasExtra := a.hasProps || a.hasAllOf |
| a.IsMap = a.hasAdditionalProps && !hasExtra |
| a.IsExtendedObject = a.hasAdditionalProps && hasExtra |
| if a.IsMap { |
| if a.schema.AdditionalProperties.Schema != nil { |
| msch, err := Schema(SchemaOpts{ |
| Schema: a.schema.AdditionalProperties.Schema, |
| Root: a.root, |
| BasePath: a.basePath, |
| }) |
| if err != nil { |
| return err |
| } |
| a.IsSimpleMap = msch.IsSimpleSchema |
| } else if a.schema.AdditionalProperties.Allows { |
| a.IsSimpleMap = true |
| } |
| } |
| } |
| return nil |
| } |
| |
| func (a *AnalyzedSchema) inferArray() error { |
| // an array has Items defined as an object schema, otherwise we qualify this JSON array as a tuple |
| // (yes, even if the Items array contains only one element). |
| // arrays in JSON schema may be unrestricted (i.e no Items specified). |
| // Note that arrays in Swagger MUST have Items. Nonetheless, we analyze unrestricted arrays. |
| // |
| // NOTE: the spec package misses the distinction between: |
| // items: [] and items: {}, so we consider both arrays here. |
| a.IsArray = a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Schemas == nil) |
| if a.IsArray && a.hasItems { |
| if a.schema.Items.Schema != nil { |
| itsch, err := Schema(SchemaOpts{ |
| Schema: a.schema.Items.Schema, |
| Root: a.root, |
| BasePath: a.basePath, |
| }) |
| if err != nil { |
| return err |
| } |
| a.IsSimpleArray = itsch.IsSimpleSchema |
| } |
| } |
| if a.IsArray && !a.hasItems { |
| a.IsSimpleArray = true |
| } |
| return nil |
| } |
| |
| func (a *AnalyzedSchema) inferTuple() error { |
| tuple := a.hasItems && a.schema.Items.Schemas != nil |
| a.IsTuple = tuple && !a.hasAdditionalItems |
| a.IsTupleWithExtra = tuple && a.hasAdditionalItems |
| return nil |
| } |
| |
| func (a *AnalyzedSchema) inferBaseType() { |
| if a.isObjectType() { |
| a.IsBaseType = a.schema.Discriminator != "" |
| } |
| } |
| |
| func (a *AnalyzedSchema) inferEnum() { |
| a.IsEnum = len(a.schema.Enum) > 0 |
| } |
| |
| func (a *AnalyzedSchema) initializeFlags() { |
| a.hasProps = len(a.schema.Properties) > 0 |
| a.hasAllOf = len(a.schema.AllOf) > 0 |
| a.hasRef = a.schema.Ref.String() != "" |
| |
| a.hasItems = a.schema.Items != nil && |
| (a.schema.Items.Schema != nil || len(a.schema.Items.Schemas) > 0) |
| |
| a.hasAdditionalProps = a.schema.AdditionalProperties != nil && |
| (a.schema.AdditionalProperties != nil || a.schema.AdditionalProperties.Allows) |
| |
| a.hasAdditionalItems = a.schema.AdditionalItems != nil && |
| (a.schema.AdditionalItems.Schema != nil || a.schema.AdditionalItems.Allows) |
| |
| } |
| |
| func (a *AnalyzedSchema) isObjectType() bool { |
| return !a.hasRef && (a.schema.Type == nil || a.schema.Type.Contains("") || a.schema.Type.Contains("object")) |
| } |
| |
| func (a *AnalyzedSchema) isArrayType() bool { |
| return !a.hasRef && (a.schema.Type != nil && a.schema.Type.Contains("array")) |
| } |