Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.0 KiB
234 lines
6.0 KiB
package analysis |
|
|
|
import ( |
|
"fmt" |
|
|
|
"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) { |
|
if opts.Schema == nil { |
|
return nil, fmt.Errorf("no schema to analyze") |
|
} |
|
|
|
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 |
|
} |
|
|
|
a.inferTuple() |
|
|
|
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 |
|
} |
|
rsch, err := Schema(SchemaOpts{ |
|
Schema: sch, |
|
Root: a.root, |
|
BasePath: a.basePath, |
|
}) |
|
if err != nil { |
|
// NOTE(fredbi): currently the only cause for errors is |
|
// 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. |
|
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() { |
|
tuple := a.hasItems && a.schema.Items.Schemas != nil |
|
a.IsTuple = tuple && !a.hasAdditionalItems |
|
a.IsTupleWithExtra = tuple && a.hasAdditionalItems |
|
} |
|
|
|
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.Schema != 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")) |
|
}
|
|
|