1
16
17 package proto
18
19 import (
20 "fmt"
21 "reflect"
22 "strings"
23
24 openapi_v3 "github.com/google/gnostic-models/openapiv3"
25 "gopkg.in/yaml.v3"
26 )
27
28
29
30 func NewOpenAPIV3Data(doc *openapi_v3.Document) (Models, error) {
31 definitions := Definitions{
32 models: map[string]Schema{},
33 }
34
35 schemas := doc.GetComponents().GetSchemas()
36 if schemas == nil {
37 return &definitions, nil
38 }
39
40
41
42 for _, namedSchema := range schemas.GetAdditionalProperties() {
43 definitions.models[namedSchema.GetName()] = nil
44 }
45
46
47 for _, namedSchema := range schemas.GetAdditionalProperties() {
48 path := NewPath(namedSchema.GetName())
49 val := namedSchema.GetValue()
50
51 if val == nil {
52 continue
53 }
54
55 if schema, err := definitions.ParseV3SchemaOrReference(namedSchema.GetValue(), &path); err != nil {
56 return nil, err
57 } else if schema != nil {
58
59
60 definitions.models[namedSchema.GetName()] = schema
61 }
62 }
63
64 return &definitions, nil
65 }
66
67 func (d *Definitions) ParseV3SchemaReference(s *openapi_v3.Reference, path *Path) (Schema, error) {
68 base := &BaseSchema{
69 Description: s.Description,
70 }
71
72 if !strings.HasPrefix(s.GetXRef(), "#/components/schemas") {
73
74
75
76 return &Arbitrary{
77 BaseSchema: *base,
78 }, nil
79 }
80
81 reference := strings.TrimPrefix(s.GetXRef(), "#/components/schemas/")
82 if _, ok := d.models[reference]; !ok {
83 return nil, newSchemaError(path, "unknown model in reference: %q", reference)
84 }
85
86 return &Ref{
87 BaseSchema: BaseSchema{
88 Description: s.Description,
89 },
90 reference: reference,
91 definitions: d,
92 }, nil
93 }
94
95 func (d *Definitions) ParseV3SchemaOrReference(s *openapi_v3.SchemaOrReference, path *Path) (Schema, error) {
96 var schema Schema
97 var err error
98
99 switch v := s.GetOneof().(type) {
100 case *openapi_v3.SchemaOrReference_Reference:
101
102
103
104
105
106
107 schema, err = d.ParseV3SchemaReference(v.Reference, path)
108 case *openapi_v3.SchemaOrReference_Schema:
109 schema, err = d.ParseSchemaV3(v.Schema, path)
110 default:
111 panic("unexpected type")
112 }
113
114 return schema, err
115 }
116
117
118
119 func (d *Definitions) ParseSchemaV3(s *openapi_v3.Schema, path *Path) (Schema, error) {
120 switch s.GetType() {
121 case object:
122 for _, extension := range s.GetSpecificationExtension() {
123 if extension.Name == "x-kubernetes-group-version-kind" {
124
125
126 return d.parseV3Kind(s, path)
127 }
128 }
129
130 if len(s.GetProperties().GetAdditionalProperties()) > 0 {
131 return d.parseV3Kind(s, path)
132 }
133 return d.parseV3Map(s, path)
134 case array:
135 return d.parseV3Array(s, path)
136 case String, Number, Integer, Boolean:
137 return d.parseV3Primitive(s, path)
138 default:
139 return d.parseV3Arbitrary(s, path)
140 }
141 }
142
143 func (d *Definitions) parseV3Kind(s *openapi_v3.Schema, path *Path) (Schema, error) {
144 if s.GetType() != object {
145 return nil, newSchemaError(path, "invalid object type")
146 } else if s.GetProperties() == nil {
147 return nil, newSchemaError(path, "object doesn't have properties")
148 }
149
150 fields := map[string]Schema{}
151 fieldOrder := []string{}
152
153 for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
154 var err error
155 name := namedSchema.GetName()
156 path := path.FieldPath(name)
157 fields[name], err = d.ParseV3SchemaOrReference(namedSchema.GetValue(), &path)
158 if err != nil {
159 return nil, err
160 }
161 fieldOrder = append(fieldOrder, name)
162 }
163
164 base, err := d.parseV3BaseSchema(s, path)
165 if err != nil {
166 return nil, err
167 }
168
169 return &Kind{
170 BaseSchema: *base,
171 RequiredFields: s.GetRequired(),
172 Fields: fields,
173 FieldOrder: fieldOrder,
174 }, nil
175 }
176
177 func (d *Definitions) parseV3Arbitrary(s *openapi_v3.Schema, path *Path) (Schema, error) {
178 base, err := d.parseV3BaseSchema(s, path)
179 if err != nil {
180 return nil, err
181 }
182 return &Arbitrary{
183 BaseSchema: *base,
184 }, nil
185 }
186
187 func (d *Definitions) parseV3Primitive(s *openapi_v3.Schema, path *Path) (Schema, error) {
188 switch s.GetType() {
189 case String:
190 case Number:
191 case Integer:
192 case Boolean:
193 default:
194
195 return d.parseV3Arbitrary(s, path)
196 }
197
198 base, err := d.parseV3BaseSchema(s, path)
199 if err != nil {
200 return nil, err
201 }
202
203 return &Primitive{
204 BaseSchema: *base,
205 Type: s.GetType(),
206 Format: s.GetFormat(),
207 }, nil
208 }
209
210 func (d *Definitions) parseV3Array(s *openapi_v3.Schema, path *Path) (Schema, error) {
211 if s.GetType() != array {
212 return nil, newSchemaError(path, `array should have type "array"`)
213 } else if len(s.GetItems().GetSchemaOrReference()) != 1 {
214
215
216
217 return d.parseV3Arbitrary(s, path)
218 }
219
220 sub, err := d.ParseV3SchemaOrReference(s.GetItems().GetSchemaOrReference()[0], path)
221 if err != nil {
222 return nil, err
223 }
224
225 base, err := d.parseV3BaseSchema(s, path)
226 if err != nil {
227 return nil, err
228 }
229 return &Array{
230 BaseSchema: *base,
231 SubType: sub,
232 }, nil
233 }
234
235
236 func (d *Definitions) parseV3Map(s *openapi_v3.Schema, path *Path) (Schema, error) {
237 if s.GetType() != object {
238 return nil, newSchemaError(path, "invalid object type")
239 }
240 var sub Schema
241
242 switch p := s.GetAdditionalProperties().GetOneof().(type) {
243 case *openapi_v3.AdditionalPropertiesItem_Boolean:
244
245 base, err := d.parseV3BaseSchema(s, path)
246 if err != nil {
247 return nil, err
248 }
249 sub = &Arbitrary{
250 BaseSchema: *base,
251 }
252 case *openapi_v3.AdditionalPropertiesItem_SchemaOrReference:
253 if schema, err := d.ParseV3SchemaOrReference(p.SchemaOrReference, path); err != nil {
254 return nil, err
255 } else {
256 sub = schema
257 }
258 case nil:
259
260 sub = &Arbitrary{}
261 default:
262 panic("unrecognized type " + reflect.TypeOf(p).Name())
263 }
264
265 base, err := d.parseV3BaseSchema(s, path)
266 if err != nil {
267 return nil, err
268 }
269 return &Map{
270 BaseSchema: *base,
271 SubType: sub,
272 }, nil
273 }
274
275 func parseV3Interface(def *yaml.Node) (interface{}, error) {
276 if def == nil {
277 return nil, nil
278 }
279 var i interface{}
280 if err := def.Decode(&i); err != nil {
281 return nil, err
282 }
283 return i, nil
284 }
285
286 func (d *Definitions) parseV3BaseSchema(s *openapi_v3.Schema, path *Path) (*BaseSchema, error) {
287 if s == nil {
288 return nil, fmt.Errorf("cannot initialize BaseSchema from nil")
289 }
290
291 def, err := parseV3Interface(s.GetDefault().ToRawInfo())
292 if err != nil {
293 return nil, err
294 }
295
296 return &BaseSchema{
297 Description: s.GetDescription(),
298 Default: def,
299 Extensions: SpecificationExtensionToMap(s.GetSpecificationExtension()),
300 Path: *path,
301 }, nil
302 }
303
304 func SpecificationExtensionToMap(e []*openapi_v3.NamedAny) map[string]interface{} {
305 values := map[string]interface{}{}
306
307 for _, na := range e {
308 if na.GetName() == "" || na.GetValue() == nil {
309 continue
310 }
311 if na.GetValue().GetYaml() == "" {
312 continue
313 }
314 var value interface{}
315 err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value)
316 if err != nil {
317 continue
318 }
319
320 values[na.GetName()] = value
321 }
322
323 return values
324 }
325
View as plain text