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