1
2
3
4
5
6 package disco
7
8 import (
9 "encoding/json"
10 "fmt"
11 "reflect"
12 "sort"
13 "strings"
14 )
15
16
17 type Document struct {
18 ID string `json:"id"`
19 Name string `json:"name"`
20 Version string `json:"version"`
21 Title string `json:"title"`
22 RootURL string `json:"rootUrl"`
23 MTLSRootURL string `json:"mtlsRootUrl"`
24 ServicePath string `json:"servicePath"`
25 BasePath string `json:"basePath"`
26 DocumentationLink string `json:"documentationLink"`
27 Auth Auth `json:"auth"`
28 Features []string `json:"features"`
29 Methods MethodList `json:"methods"`
30 Schemas map[string]*Schema `json:"schemas"`
31 Resources ResourceList `json:"resources"`
32 }
33
34
35
36 func (d *Document) init() error {
37 schemasByID := map[string]*Schema{}
38 for _, s := range d.Schemas {
39 schemasByID[s.ID] = s
40 }
41 for name, s := range d.Schemas {
42 if s.Ref != "" {
43 return fmt.Errorf("top level schema %q is a reference", name)
44 }
45 s.Name = name
46 if err := s.init(schemasByID); err != nil {
47 return err
48 }
49 }
50 for _, m := range d.Methods {
51 if err := m.init(schemasByID); err != nil {
52 return err
53 }
54 }
55 for _, r := range d.Resources {
56 if err := r.init("", schemasByID); err != nil {
57 return err
58 }
59 }
60 return nil
61 }
62
63
64
65 func NewDocument(bytes []byte) (*Document, error) {
66
67
68 var errDoc struct {
69 Error struct {
70 Code int
71 Message string
72 Status string
73 }
74 }
75 if err := json.Unmarshal(bytes, &errDoc); err == nil && errDoc.Error.Code != 0 {
76 return nil, fmt.Errorf("bad discovery doc: %+v", errDoc.Error)
77 }
78
79 var doc Document
80 if err := json.Unmarshal(bytes, &doc); err != nil {
81 return nil, err
82 }
83 if err := doc.init(); err != nil {
84 return nil, err
85 }
86 return &doc, nil
87 }
88
89
90
91 type Auth struct {
92 OAuth2Scopes []Scope
93 }
94
95
96 type Scope struct {
97 ID string
98 Description string
99 }
100
101
102 func (a *Auth) UnmarshalJSON(data []byte) error {
103
104
105 var m struct {
106 OAuth2 struct {
107 Scopes map[string]struct {
108 Description string
109 }
110 }
111 }
112 if err := json.Unmarshal(data, &m); err != nil {
113 return err
114 }
115
116 for _, k := range sortedKeys(m.OAuth2.Scopes) {
117 a.OAuth2Scopes = append(a.OAuth2Scopes, Scope{
118 ID: k,
119 Description: m.OAuth2.Scopes[k].Description,
120 })
121 }
122 return nil
123 }
124
125
126
127
128 type Schema struct {
129 ID string
130 Type string
131 Format string
132 Description string
133 Properties PropertyList
134 ItemSchema *Schema `json:"items"`
135 AdditionalProperties *Schema
136 Ref string `json:"$ref"`
137 Default string
138 Pattern string
139 Enums []string `json:"enum"`
140
141 EnumDescriptions []string
142 Variant *Variant
143
144 RefSchema *Schema `json:"-"`
145 Name string `json:"-"`
146 Kind Kind `json:"-"`
147 }
148
149 type Variant struct {
150 Discriminant string
151 Map []*VariantMapItem
152 }
153
154 type VariantMapItem struct {
155 TypeValue string `json:"type_value"`
156 Ref string `json:"$ref"`
157 }
158
159 func (s *Schema) init(topLevelSchemas map[string]*Schema) error {
160 if s == nil {
161 return nil
162 }
163 var err error
164 if s.Ref != "" {
165 if s.RefSchema, err = resolveRef(s.Ref, topLevelSchemas); err != nil {
166 return err
167 }
168 }
169 s.Kind, err = s.initKind()
170 if err != nil {
171 return err
172 }
173 if s.Kind == ArrayKind && s.ItemSchema == nil {
174 return fmt.Errorf("schema %+v: array does not have items", s)
175 }
176 if s.Kind != ArrayKind && s.ItemSchema != nil {
177 return fmt.Errorf("schema %+v: non-array has items", s)
178 }
179 if err := s.AdditionalProperties.init(topLevelSchemas); err != nil {
180 return err
181 }
182 if err := s.ItemSchema.init(topLevelSchemas); err != nil {
183 return err
184 }
185 for _, p := range s.Properties {
186 if err := p.Schema.init(topLevelSchemas); err != nil {
187 return err
188 }
189 }
190 return nil
191 }
192
193 func resolveRef(ref string, topLevelSchemas map[string]*Schema) (*Schema, error) {
194 rs, ok := topLevelSchemas[ref]
195 if !ok {
196 return nil, fmt.Errorf("could not resolve schema reference %q", ref)
197 }
198 return rs, nil
199 }
200
201 func (s *Schema) initKind() (Kind, error) {
202 if s.Ref != "" {
203 return ReferenceKind, nil
204 }
205 switch s.Type {
206 case "string", "number", "integer", "boolean", "any":
207 return SimpleKind, nil
208 case "object":
209 if s.AdditionalProperties != nil {
210 if s.AdditionalProperties.Type == "any" {
211 return AnyStructKind, nil
212 }
213 return MapKind, nil
214 }
215 return StructKind, nil
216 case "array":
217 return ArrayKind, nil
218 default:
219 return 0, fmt.Errorf("unknown type %q for schema %q", s.Type, s.ID)
220 }
221 }
222
223
224
225
226
227
228 func (s *Schema) ElementSchema() *Schema {
229 switch s.Kind {
230 case MapKind:
231 return s.AdditionalProperties
232 case ArrayKind:
233 return s.ItemSchema
234 default:
235 panic("ElementSchema called on schema of type " + s.Type)
236 }
237 }
238
239
240
241 func (s *Schema) IsIntAsString() bool {
242 return s.Type == "string" && strings.Contains(s.Format, "int")
243 }
244
245
246 type Kind int
247
248 const (
249
250
251
252 SimpleKind Kind = iota
253
254
255
256 StructKind
257
258
259
260
261 MapKind
262
263
264
265
266 AnyStructKind
267
268
269
270 ArrayKind
271
272
273
274
275
276
277 ReferenceKind
278 )
279
280 type Property struct {
281 Name string
282 Schema *Schema
283 }
284
285 type PropertyList []*Property
286
287 func (pl *PropertyList) UnmarshalJSON(data []byte) error {
288
289 var m map[string]*Schema
290 if err := json.Unmarshal(data, &m); err != nil {
291 return err
292 }
293 for _, k := range sortedKeys(m) {
294 *pl = append(*pl, &Property{
295 Name: k,
296 Schema: m[k],
297 })
298 }
299 return nil
300 }
301
302 type ResourceList []*Resource
303
304 func (rl *ResourceList) UnmarshalJSON(data []byte) error {
305
306 var m map[string]*Resource
307 if err := json.Unmarshal(data, &m); err != nil {
308 return err
309 }
310 for _, k := range sortedKeys(m) {
311 r := m[k]
312 r.Name = k
313 *rl = append(*rl, r)
314 }
315 return nil
316 }
317
318
319 type Resource struct {
320 Name string
321 FullName string
322 Methods MethodList
323 Resources ResourceList
324 }
325
326 func (r *Resource) init(parentFullName string, topLevelSchemas map[string]*Schema) error {
327 r.FullName = fmt.Sprintf("%s.%s", parentFullName, r.Name)
328 for _, m := range r.Methods {
329 if err := m.init(topLevelSchemas); err != nil {
330 return err
331 }
332 }
333 for _, r2 := range r.Resources {
334 if err := r2.init(r.FullName, topLevelSchemas); err != nil {
335 return err
336 }
337 }
338 return nil
339 }
340
341 type MethodList []*Method
342
343 func (ml *MethodList) UnmarshalJSON(data []byte) error {
344
345 var m map[string]*Method
346 if err := json.Unmarshal(data, &m); err != nil {
347 return err
348 }
349 for _, k := range sortedKeys(m) {
350 meth := m[k]
351 meth.Name = k
352 *ml = append(*ml, meth)
353 }
354 return nil
355 }
356
357
358 type Method struct {
359 Name string
360 ID string
361 Path string
362 HTTPMethod string
363 Description string
364 Parameters ParameterList
365 ParameterOrder []string
366 Request *Schema
367 Response *Schema
368 Scopes []string
369 MediaUpload *MediaUpload
370 SupportsMediaDownload bool
371 APIVersion string
372
373 JSONMap map[string]interface{} `json:"-"`
374 }
375
376 type MediaUpload struct {
377 Accept []string
378 MaxSize string
379 Protocols map[string]Protocol
380 }
381
382 type Protocol struct {
383 Multipart bool
384 Path string
385 }
386
387 func (m *Method) init(topLevelSchemas map[string]*Schema) error {
388 if err := m.Request.init(topLevelSchemas); err != nil {
389 return err
390 }
391 if err := m.Response.init(topLevelSchemas); err != nil {
392 return err
393 }
394 return nil
395 }
396
397 func (m *Method) UnmarshalJSON(data []byte) error {
398 type T Method
399 if err := json.Unmarshal(data, (*T)(m)); err != nil {
400 return err
401 }
402
403
404
405 return json.Unmarshal(data, &m.JSONMap)
406 }
407
408 type ParameterList []*Parameter
409
410 func (pl *ParameterList) UnmarshalJSON(data []byte) error {
411
412 var m map[string]*Parameter
413 if err := json.Unmarshal(data, &m); err != nil {
414 return err
415 }
416 for _, k := range sortedKeys(m) {
417 p := m[k]
418 p.Name = k
419 *pl = append(*pl, p)
420 }
421 return nil
422 }
423
424
425 type Parameter struct {
426 Name string
427 Schema
428 Required bool
429 Repeated bool
430 Location string
431 }
432
433
434 func sortedKeys(m interface{}) []string {
435 vkeys := reflect.ValueOf(m).MapKeys()
436 var keys []string
437 for _, vk := range vkeys {
438 keys = append(keys, vk.Interface().(string))
439 }
440 sort.Strings(keys)
441 return keys
442 }
443
View as plain text