1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package swag
16
17 import (
18 "encoding/json"
19 "net/http"
20 "testing"
21
22 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/require"
24 yaml "gopkg.in/yaml.v3"
25 )
26
27 func TestJSONToYAML(t *testing.T) {
28 sd := `{"1":"the int key value","name":"a string value","y":"some value"}`
29 var data JSONMapSlice
30 require.NoError(t, json.Unmarshal([]byte(sd), &data))
31
32 y, err := data.MarshalYAML()
33 require.NoError(t, err)
34 const expected = `"1": the int key value
35 name: a string value
36 y: some value
37 `
38 assert.Equal(t, expected, string(y.([]byte)))
39
40 nstd := `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`
41 const nestpected = `"1": the int key value
42 name: a string value
43 y: some value
44 tag:
45 name: tag name
46 `
47 var ndata JSONMapSlice
48 require.NoError(t, json.Unmarshal([]byte(nstd), &ndata))
49 ny, err := ndata.MarshalYAML()
50 require.NoError(t, err)
51 assert.Equal(t, nestpected, string(ny.([]byte)))
52
53 ydoc, err := BytesToYAMLDoc([]byte(fixtures2224))
54 require.NoError(t, err)
55 b, err := YAMLToJSON(ydoc)
56 require.NoError(t, err)
57
58 var bdata JSONMapSlice
59 require.NoError(t, json.Unmarshal(b, &bdata))
60
61 }
62
63 func TestJSONToYAMLWithNull(t *testing.T) {
64 const (
65 jazon = `{"1":"the int key value","name":null,"y":"some value"}`
66 expected = `"1": the int key value
67 name: null
68 y: some value
69 `
70 )
71 var data JSONMapSlice
72 require.NoError(t, json.Unmarshal([]byte(jazon), &data))
73 ny, err := data.MarshalYAML()
74 require.NoError(t, err)
75 assert.Equal(t, expected, string(ny.([]byte)))
76 }
77
78 func TestMarshalYAML(t *testing.T) {
79 t.Run("marshalYAML should be deterministic", func(t *testing.T) {
80 const (
81 jazon = `{"1":"x","2":null,"3":{"a":1,"b":2,"c":3}}`
82 expected = `"1": x
83 "2": null
84 "3":
85 a: !!float 1
86 b: !!float 2
87 c: !!float 3
88 `
89 )
90 const iterations = 10
91 for n := 0; n < iterations; n++ {
92 var data JSONMapSlice
93 require.NoError(t, json.Unmarshal([]byte(jazon), &data))
94 ny, err := data.MarshalYAML()
95 require.NoError(t, err)
96 assert.Equal(t, expected, string(ny.([]byte)))
97 }
98 })
99 }
100
101 func TestYAMLToJSON(t *testing.T) {
102 sd := `---
103 1: the int key value
104 name: a string value
105 'y': some value
106 `
107 var data yaml.Node
108 _ = yaml.Unmarshal([]byte(sd), &data)
109
110 d, err := YAMLToJSON(data)
111 require.NoError(t, err)
112 require.NotNil(t, d)
113 assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value"}`, string(d))
114
115 ns := []*yaml.Node{
116 {
117 Kind: yaml.ScalarNode,
118 Value: "true",
119 Tag: "!!bool",
120 },
121 {
122 Kind: yaml.ScalarNode,
123 Value: "the bool value",
124 Tag: "!!str",
125 },
126 }
127 data.Content[0].Content = append(data.Content[0].Content, ns...)
128 d, err = YAMLToJSON(data)
129 require.Error(t, err)
130 require.Nil(t, d)
131
132 data.Content[0].Content = data.Content[0].Content[:len(data.Content[0].Content)-2]
133
134 tag := []*yaml.Node{
135 {
136 Kind: yaml.ScalarNode,
137 Value: "tag",
138 Tag: "!!str",
139 },
140 {
141 Kind: yaml.MappingNode,
142 Content: []*yaml.Node{
143 {
144 Kind: yaml.ScalarNode,
145 Value: "name",
146 Tag: "!!str",
147 },
148 {
149 Kind: yaml.ScalarNode,
150 Value: "tag name",
151 Tag: "!!str",
152 },
153 },
154 },
155 }
156 data.Content[0].Content = append(data.Content[0].Content, tag...)
157
158 d, err = YAMLToJSON(data)
159 require.NoError(t, err)
160 assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`, string(d))
161
162 tag[1].Content = []*yaml.Node{
163 {
164 Kind: yaml.ScalarNode,
165 Value: "true",
166 Tag: "!!bool",
167 },
168 {
169 Kind: yaml.ScalarNode,
170 Value: "the bool tag name",
171 Tag: "!!str",
172 },
173 }
174
175 d, err = YAMLToJSON(data)
176 require.Error(t, err)
177 require.Nil(t, d)
178
179 var lst []interface{}
180 lst = append(lst, "hello")
181
182 d, err = YAMLToJSON(lst)
183 require.NoError(t, err)
184 require.NotNil(t, d)
185 assert.Equal(t, []byte(`["hello"]`), []byte(d))
186
187 lst = append(lst, data)
188
189 d, err = YAMLToJSON(lst)
190 require.Error(t, err)
191 require.Nil(t, d)
192
193 _, err = BytesToYAMLDoc([]byte("- name: hello\n"))
194 require.Error(t, err)
195
196 dd, err := BytesToYAMLDoc([]byte("description: 'object created'\n"))
197 require.NoError(t, err)
198
199 d, err = YAMLToJSON(dd)
200 require.NoError(t, err)
201 assert.Equal(t, json.RawMessage(`{"description":"object created"}`), d)
202 }
203
204 var yamlPestoreServer = func(rw http.ResponseWriter, _ *http.Request) {
205 rw.WriteHeader(http.StatusOK)
206 _, _ = rw.Write([]byte(yamlPetStore))
207 }
208
209 func TestWithYKey(t *testing.T) {
210 doc, err := BytesToYAMLDoc([]byte(withYKey))
211 require.NoError(t, err)
212
213 _, err = YAMLToJSON(doc)
214 require.NoError(t, err)
215
216 doc, err = BytesToYAMLDoc([]byte(withQuotedYKey))
217 require.NoError(t, err)
218 jsond, err := YAMLToJSON(doc)
219 require.NoError(t, err)
220
221 var yt struct {
222 Definitions struct {
223 Viewbox struct {
224 Properties struct {
225 Y struct {
226 Type string `json:"type"`
227 } `json:"y"`
228 } `json:"properties"`
229 } `json:"viewbox"`
230 } `json:"definitions"`
231 }
232 require.NoError(t, json.Unmarshal(jsond, &yt))
233 assert.Equal(t, "integer", yt.Definitions.Viewbox.Properties.Y.Type)
234 }
235
236 func TestMapKeyTypes(t *testing.T) {
237 dm := map[interface{}]interface{}{
238 12345: "int",
239 int8(1): "int8",
240 int16(12345): "int16",
241 int32(12345678): "int32",
242 int64(12345678910): "int64",
243 uint(12345): "uint",
244 uint8(1): "uint8",
245 uint16(12345): "uint16",
246 uint32(12345678): "uint32",
247 uint64(12345678910): "uint64",
248 }
249 _, err := YAMLToJSON(dm)
250 require.NoError(t, err)
251 }
252
253 const fixtures2224 = `definitions:
254 Time:
255 type: string
256 format: date-time
257 x-go-type:
258 import:
259 package: time
260 embedded: true
261 type: Time
262 x-nullable: true
263
264 TimeAsObject: # <- time.Time is actually a struct
265 type: string
266 format: date-time
267 x-go-type:
268 import:
269 package: time
270 hints:
271 kind: object
272 embedded: true
273 type: Time
274 x-nullable: true
275
276 Raw:
277 x-go-type:
278 import:
279 package: encoding/json
280 hints:
281 kind: primitive
282 embedded: true
283 type: RawMessage
284
285 Request:
286 x-go-type:
287 import:
288 package: net/http
289 hints:
290 kind: object
291 embedded: true
292 type: Request
293
294 RequestPointer:
295 x-go-type:
296 import:
297 package: net/http
298 hints:
299 kind: object
300 nullable: true
301 embedded: true
302 type: Request
303
304 OldStyleImport:
305 type: object
306 x-go-type:
307 import:
308 package: net/http
309 type: Request
310 hints:
311 noValidation: true
312
313 OldStyleRenamed:
314 type: object
315 x-go-type:
316 import:
317 package: net/http
318 type: Request
319 hints:
320 noValidation: true
321 x-go-name: OldRenamed
322
323 ObjectWithEmbedded:
324 type: object
325 properties:
326 a:
327 $ref: '#/definitions/Time'
328 b:
329 $ref: '#/definitions/Request'
330 c:
331 $ref: '#/definitions/TimeAsObject'
332 d:
333 $ref: '#/definitions/Raw'
334 e:
335 $ref: '#/definitions/JSONObject'
336 f:
337 $ref: '#/definitions/JSONMessage'
338 g:
339 $ref: '#/definitions/JSONObjectWithAlias'
340
341 ObjectWithExternals:
342 type: object
343 properties:
344 a:
345 $ref: '#/definitions/OldStyleImport'
346 b:
347 $ref: '#/definitions/OldStyleRenamed'
348
349 Base:
350 properties: &base
351 id:
352 type: integer
353 format: uint64
354 x-go-custom-tag: 'gorm:"primary_key"'
355 FBID:
356 type: integer
357 format: uint64
358 x-go-custom-tag: 'gorm:"index"'
359 created_at:
360 $ref: "#/definitions/Time"
361 updated_at:
362 $ref: "#/definitions/Time"
363 version:
364 type: integer
365 format: uint64
366
367 HotspotType:
368 type: string
369 enum:
370 - A
371 - B
372 - C
373
374 Hotspot:
375 type: object
376 allOf:
377 - properties: *base
378 - properties:
379 access_points:
380 type: array
381 items:
382 $ref: '#/definitions/AccessPoint'
383 type:
384 $ref: '#/definitions/HotspotType'
385 required:
386 - type
387
388 AccessPoint:
389 type: object
390 allOf:
391 - properties: *base
392 - properties:
393 mac_address:
394 type: string
395 x-go-custom-tag: 'gorm:"index;not null;unique"'
396 hotspot_id:
397 type: integer
398 format: uint64
399 hotspot:
400 $ref: '#/definitions/Hotspot'
401
402 JSONObject:
403 type: object
404 additionalProperties:
405 type: array
406 items:
407 $ref: '#/definitions/Raw'
408
409 JSONObjectWithAlias:
410 type: object
411 additionalProperties:
412 type: object
413 properties:
414 message:
415 $ref: '#/definitions/JSONMessage'
416
417 JSONMessage:
418 $ref: '#/definitions/Raw'
419
420 Incorrect:
421 x-go-type:
422 import:
423 package: net
424 hints:
425 kind: array
426 embedded: true
427 type: Buffers
428 x-nullable: true
429 `
430
431 const withQuotedYKey = `consumes:
432 - application/json
433 definitions:
434 viewBox:
435 type: object
436 properties:
437 x:
438 type: integer
439 format: int16
440 # y -> types don't match: expect map key string or int get: bool
441 "y":
442 type: integer
443 format: int16
444 width:
445 type: integer
446 format: int16
447 height:
448 type: integer
449 format: int16
450 info:
451 description: Test RESTful APIs
452 title: Test Server
453 version: 1.0.0
454 basePath: /api
455 paths:
456 /test:
457 get:
458 operationId: findAll
459 parameters:
460 - name: since
461 in: query
462 type: integer
463 format: int64
464 - name: limit
465 in: query
466 type: integer
467 format: int32
468 default: 20
469 responses:
470 200:
471 description: Array[Trigger]
472 schema:
473 type: array
474 items:
475 $ref: "#/definitions/viewBox"
476 produces:
477 - application/json
478 schemes:
479 - https
480 swagger: "2.0"
481 `
482
483 const withYKey = `consumes:
484 - application/json
485 definitions:
486 viewBox:
487 type: object
488 properties:
489 x:
490 type: integer
491 format: int16
492 # y -> types don't match: expect map key string or int get: bool
493 y:
494 type: integer
495 format: int16
496 width:
497 type: integer
498 format: int16
499 height:
500 type: integer
501 format: int16
502 info:
503 description: Test RESTful APIs
504 title: Test Server
505 version: 1.0.0
506 basePath: /api
507 paths:
508 /test:
509 get:
510 operationId: findAll
511 parameters:
512 - name: since
513 in: query
514 type: integer
515 format: int64
516 - name: limit
517 in: query
518 type: integer
519 format: int32
520 default: 20
521 responses:
522 200:
523 description: Array[Trigger]
524 schema:
525 type: array
526 items:
527 $ref: "#/definitions/viewBox"
528 produces:
529 - application/json
530 schemes:
531 - https
532 swagger: "2.0"
533 `
534
535 const yamlPetStore = `swagger: '2.0'
536 info:
537 version: '1.0.0'
538 title: Swagger Petstore
539 description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
540 termsOfService: http://helloreverb.com/terms/
541 contact:
542 name: Swagger API team
543 email: foo@example.com
544 url: http://swagger.io
545 license:
546 name: MIT
547 url: http://opensource.org/licenses/MIT
548 host: petstore.swagger.wordnik.com
549 basePath: /api
550 schemes:
551 - http
552 consumes:
553 - application/json
554 produces:
555 - application/json
556 paths:
557 /pets:
558 get:
559 description: Returns all pets from the system that the user has access to
560 operationId: findPets
561 produces:
562 - application/json
563 - application/xml
564 - text/xml
565 - text/html
566 parameters:
567 - name: tags
568 in: query
569 description: tags to filter by
570 required: false
571 type: array
572 items:
573 type: string
574 collectionFormat: csv
575 - name: limit
576 in: query
577 description: maximum number of results to return
578 required: false
579 type: integer
580 format: int32
581 responses:
582 '200':
583 description: pet response
584 schema:
585 type: array
586 items:
587 $ref: '#/definitions/pet'
588 default:
589 description: unexpected error
590 schema:
591 $ref: '#/definitions/errorModel'
592 post:
593 description: Creates a new pet in the store. Duplicates are allowed
594 operationId: addPet
595 produces:
596 - application/json
597 parameters:
598 - name: pet
599 in: body
600 description: Pet to add to the store
601 required: true
602 schema:
603 $ref: '#/definitions/newPet'
604 responses:
605 '200':
606 description: pet response
607 schema:
608 $ref: '#/definitions/pet'
609 default:
610 description: unexpected error
611 schema:
612 $ref: '#/definitions/errorModel'
613 /pets/{id}:
614 get:
615 description: Returns a user based on a single ID, if the user does not have access to the pet
616 operationId: findPetById
617 produces:
618 - application/json
619 - application/xml
620 - text/xml
621 - text/html
622 parameters:
623 - name: id
624 in: path
625 description: ID of pet to fetch
626 required: true
627 type: integer
628 format: int64
629 responses:
630 '200':
631 description: pet response
632 schema:
633 $ref: '#/definitions/pet'
634 default:
635 description: unexpected error
636 schema:
637 $ref: '#/definitions/errorModel'
638 delete:
639 description: deletes a single pet based on the ID supplied
640 operationId: deletePet
641 parameters:
642 - name: id
643 in: path
644 description: ID of pet to delete
645 required: true
646 type: integer
647 format: int64
648 responses:
649 '204':
650 description: pet deleted
651 default:
652 description: unexpected error
653 schema:
654 $ref: '#/definitions/errorModel'
655 definitions:
656 pet:
657 required:
658 - id
659 - name
660 properties:
661 id:
662 type: integer
663 format: int64
664 name:
665 type: string
666 tag:
667 type: string
668 newPet:
669 allOf:
670 - $ref: '#/definitions/pet'
671 - required:
672 - name
673 properties:
674 id:
675 type: integer
676 format: int64
677 name:
678 type: string
679 errorModel:
680 required:
681 - code
682 - message
683 properties:
684 code:
685 type: integer
686 format: int32
687 message:
688 type: string
689 `
690
View as plain text