mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 05:22:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package analysis
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 
 | |
| 	"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, errors.New("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() {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	hasExtra := a.hasProps || a.hasAllOf
 | |
| 	a.IsMap = a.hasAdditionalProps && !hasExtra
 | |
| 	a.IsExtendedObject = a.hasAdditionalProps && hasExtra
 | |
| 
 | |
| 	if !a.IsMap {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// maps
 | |
| 	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"))
 | |
| }
 | |
| 
 | |
| // isAnalyzedAsComplex determines if an analyzed schema is eligible to flattening (i.e. it is "complex").
 | |
| //
 | |
| // Complex means the schema is any of:
 | |
| //   - a simple type (primitive)
 | |
| //   - an array of something (items are possibly complex ; if this is the case, items will generate a definition)
 | |
| //   - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will
 | |
| //     generate a definition)
 | |
| func (a *AnalyzedSchema) isAnalyzedAsComplex() bool {
 | |
| 	return !a.IsSimpleSchema && !a.IsArray && !a.IsMap
 | |
| }
 |