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