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 { 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 { opts := &spec.ExpandOptions{RelativeBase: a.basePath} sch, err := spec.ResolveRefWithBase(a.root, &a.schema.Ref, opts) if err != nil { return err } if sch != nil { 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 { fromValid := a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Len() < 2) a.IsArray = fromValid || (a.hasItems && a.schema.Items.Len() < 2) 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 len(a.schema.Items.Schemas) > 0 { itsch, err := Schema(SchemaOpts{ Schema: &a.schema.Items.Schemas[0], 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.Len() > 1 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")) }