blob: c0b77c56d677023586f291bd326b44e4ce428382 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001package analysis
2
3import (
4 "github.com/go-openapi/spec"
5 "github.com/go-openapi/strfmt"
6)
7
8// SchemaOpts configures the schema analyzer
9type SchemaOpts struct {
10 Schema *spec.Schema
11 Root interface{}
12 BasePath string
13 _ struct{}
14}
15
16// Schema analysis, will classify the schema according to known
17// patterns.
18func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
19 a := &AnalyzedSchema{
20 schema: opts.Schema,
21 root: opts.Root,
22 basePath: opts.BasePath,
23 }
24
25 a.initializeFlags()
26 a.inferKnownType()
27 a.inferEnum()
28 a.inferBaseType()
29
30 if err := a.inferMap(); err != nil {
31 return nil, err
32 }
33 if err := a.inferArray(); err != nil {
34 return nil, err
35 }
36
37 if err := a.inferTuple(); err != nil {
38 // NOTE(fredbi): currently, inferTuple() never returns an error
39 return nil, err
40 }
41
42 if err := a.inferFromRef(); err != nil {
43 return nil, err
44 }
45
46 a.inferSimpleSchema()
47 return a, nil
48}
49
50// AnalyzedSchema indicates what the schema represents
51type AnalyzedSchema struct {
52 schema *spec.Schema
53 root interface{}
54 basePath string
55
56 hasProps bool
57 hasAllOf bool
58 hasItems bool
59 hasAdditionalProps bool
60 hasAdditionalItems bool
61 hasRef bool
62
63 IsKnownType bool
64 IsSimpleSchema bool
65 IsArray bool
66 IsSimpleArray bool
67 IsMap bool
68 IsSimpleMap bool
69 IsExtendedObject bool
70 IsTuple bool
71 IsTupleWithExtra bool
72 IsBaseType bool
73 IsEnum bool
74}
75
76// Inherits copies value fields from other onto this schema
77func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) {
78 if other == nil {
79 return
80 }
81 a.hasProps = other.hasProps
82 a.hasAllOf = other.hasAllOf
83 a.hasItems = other.hasItems
84 a.hasAdditionalItems = other.hasAdditionalItems
85 a.hasAdditionalProps = other.hasAdditionalProps
86 a.hasRef = other.hasRef
87
88 a.IsKnownType = other.IsKnownType
89 a.IsSimpleSchema = other.IsSimpleSchema
90 a.IsArray = other.IsArray
91 a.IsSimpleArray = other.IsSimpleArray
92 a.IsMap = other.IsMap
93 a.IsSimpleMap = other.IsSimpleMap
94 a.IsExtendedObject = other.IsExtendedObject
95 a.IsTuple = other.IsTuple
96 a.IsTupleWithExtra = other.IsTupleWithExtra
97 a.IsBaseType = other.IsBaseType
98 a.IsEnum = other.IsEnum
99}
100
101func (a *AnalyzedSchema) inferFromRef() error {
102 if a.hasRef {
103 sch := new(spec.Schema)
104 sch.Ref = a.schema.Ref
105 err := spec.ExpandSchema(sch, a.root, nil)
106 if err != nil {
107 return err
108 }
109 if sch != nil {
110 // NOTE(fredbi): currently the only cause for errors in
111 // unresolved ref. Since spec.ExpandSchema() expands the
112 // schema recursively, there is no chance to get there,
113 // until we add more causes for error in this schema analysis.
114 rsch, err := Schema(SchemaOpts{
115 Schema: sch,
116 Root: a.root,
117 BasePath: a.basePath,
118 })
119 if err != nil {
120 return err
121 }
122 a.inherits(rsch)
123 }
124 }
125 return nil
126}
127
128func (a *AnalyzedSchema) inferSimpleSchema() {
129 a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap
130}
131
132func (a *AnalyzedSchema) inferKnownType() {
133 tpe := a.schema.Type
134 format := a.schema.Format
135 a.IsKnownType = tpe.Contains("boolean") ||
136 tpe.Contains("integer") ||
137 tpe.Contains("number") ||
138 tpe.Contains("string") ||
139 (format != "" && strfmt.Default.ContainsName(format)) ||
140 (a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems)
141}
142
143func (a *AnalyzedSchema) inferMap() error {
144 if a.isObjectType() {
145 hasExtra := a.hasProps || a.hasAllOf
146 a.IsMap = a.hasAdditionalProps && !hasExtra
147 a.IsExtendedObject = a.hasAdditionalProps && hasExtra
148 if a.IsMap {
149 if a.schema.AdditionalProperties.Schema != nil {
150 msch, err := Schema(SchemaOpts{
151 Schema: a.schema.AdditionalProperties.Schema,
152 Root: a.root,
153 BasePath: a.basePath,
154 })
155 if err != nil {
156 return err
157 }
158 a.IsSimpleMap = msch.IsSimpleSchema
159 } else if a.schema.AdditionalProperties.Allows {
160 a.IsSimpleMap = true
161 }
162 }
163 }
164 return nil
165}
166
167func (a *AnalyzedSchema) inferArray() error {
168 // an array has Items defined as an object schema, otherwise we qualify this JSON array as a tuple
169 // (yes, even if the Items array contains only one element).
170 // arrays in JSON schema may be unrestricted (i.e no Items specified).
171 // Note that arrays in Swagger MUST have Items. Nonetheless, we analyze unrestricted arrays.
172 //
173 // NOTE: the spec package misses the distinction between:
174 // items: [] and items: {}, so we consider both arrays here.
175 a.IsArray = a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Schemas == nil)
176 if a.IsArray && a.hasItems {
177 if a.schema.Items.Schema != nil {
178 itsch, err := Schema(SchemaOpts{
179 Schema: a.schema.Items.Schema,
180 Root: a.root,
181 BasePath: a.basePath,
182 })
183 if err != nil {
184 return err
185 }
186 a.IsSimpleArray = itsch.IsSimpleSchema
187 }
188 }
189 if a.IsArray && !a.hasItems {
190 a.IsSimpleArray = true
191 }
192 return nil
193}
194
195func (a *AnalyzedSchema) inferTuple() error {
196 tuple := a.hasItems && a.schema.Items.Schemas != nil
197 a.IsTuple = tuple && !a.hasAdditionalItems
198 a.IsTupleWithExtra = tuple && a.hasAdditionalItems
199 return nil
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 != 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}