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