1 package genswagger
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "math"
8 "reflect"
9 "strings"
10 "testing"
11
12 "github.com/golang/protobuf/proto"
13 protodescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
14 plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
15 structpb "github.com/golang/protobuf/ptypes/struct"
16 "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
17 "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule"
18 swagger_options "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options"
19 )
20
21 func crossLinkFixture(f *descriptor.File) *descriptor.File {
22 for _, m := range f.Messages {
23 m.File = f
24 }
25 for _, svc := range f.Services {
26 svc.File = f
27 for _, m := range svc.Methods {
28 m.Service = svc
29 for _, b := range m.Bindings {
30 b.Method = m
31 for _, param := range b.PathParams {
32 param.Method = m
33 }
34 }
35 }
36 }
37 return f
38 }
39
40 func reqFromFile(f *descriptor.File) *plugin.CodeGeneratorRequest {
41 return &plugin.CodeGeneratorRequest{
42 ProtoFile: []*protodescriptor.FileDescriptorProto{
43 f.FileDescriptorProto,
44 },
45 FileToGenerate: []string{f.GetName()},
46 }
47 }
48
49 func TestMessageToQueryParametersWithEnumAsInt(t *testing.T) {
50 type test struct {
51 MsgDescs []*protodescriptor.DescriptorProto
52 Message string
53 Params []swaggerParameterObject
54 }
55
56 tests := []test{
57 {
58 MsgDescs: []*protodescriptor.DescriptorProto{
59 &protodescriptor.DescriptorProto{
60 Name: proto.String("ExampleMessage"),
61 Field: []*protodescriptor.FieldDescriptorProto{
62 {
63 Name: proto.String("a"),
64 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
65 Number: proto.Int32(1),
66 },
67 {
68 Name: proto.String("b"),
69 Type: protodescriptor.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
70 Number: proto.Int32(2),
71 },
72 {
73 Name: proto.String("c"),
74 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
75 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
76 Number: proto.Int32(3),
77 },
78 },
79 },
80 },
81 Message: "ExampleMessage",
82 Params: []swaggerParameterObject{
83 swaggerParameterObject{
84 Name: "a",
85 In: "query",
86 Required: false,
87 Type: "string",
88 },
89 swaggerParameterObject{
90 Name: "b",
91 In: "query",
92 Required: false,
93 Type: "number",
94 Format: "double",
95 },
96 swaggerParameterObject{
97 Name: "c",
98 In: "query",
99 Required: false,
100 Type: "array",
101 CollectionFormat: "multi",
102 },
103 },
104 },
105 {
106 MsgDescs: []*protodescriptor.DescriptorProto{
107 &protodescriptor.DescriptorProto{
108 Name: proto.String("ExampleMessage"),
109 Field: []*protodescriptor.FieldDescriptorProto{
110 {
111 Name: proto.String("nested"),
112 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
113 TypeName: proto.String(".example.Nested"),
114 Number: proto.Int32(1),
115 },
116 },
117 },
118 &protodescriptor.DescriptorProto{
119 Name: proto.String("Nested"),
120 Field: []*protodescriptor.FieldDescriptorProto{
121 {
122 Name: proto.String("a"),
123 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
124 Number: proto.Int32(1),
125 },
126 {
127 Name: proto.String("deep"),
128 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
129 TypeName: proto.String(".example.Nested.DeepNested"),
130 Number: proto.Int32(2),
131 },
132 },
133 NestedType: []*protodescriptor.DescriptorProto{{
134 Name: proto.String("DeepNested"),
135 Field: []*protodescriptor.FieldDescriptorProto{
136 {
137 Name: proto.String("b"),
138 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
139 Number: proto.Int32(1),
140 },
141 {
142 Name: proto.String("c"),
143 Type: protodescriptor.FieldDescriptorProto_TYPE_ENUM.Enum(),
144 TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
145 Number: proto.Int32(2),
146 },
147 },
148 EnumType: []*protodescriptor.EnumDescriptorProto{
149 {
150 Name: proto.String("DeepEnum"),
151 Value: []*protodescriptor.EnumValueDescriptorProto{
152 {Name: proto.String("FALSE"), Number: proto.Int32(0)},
153 {Name: proto.String("TRUE"), Number: proto.Int32(1)},
154 },
155 },
156 },
157 }},
158 },
159 },
160 Message: "ExampleMessage",
161 Params: []swaggerParameterObject{
162 swaggerParameterObject{
163 Name: "nested.a",
164 In: "query",
165 Required: false,
166 Type: "string",
167 },
168 swaggerParameterObject{
169 Name: "nested.deep.b",
170 In: "query",
171 Required: false,
172 Type: "string",
173 },
174 swaggerParameterObject{
175 Name: "nested.deep.c",
176 In: "query",
177 Required: false,
178 Type: "integer",
179 Enum: []string{"0", "1"},
180 Default: "0",
181 },
182 },
183 },
184 }
185
186 for _, test := range tests {
187 reg := descriptor.NewRegistry()
188 reg.SetEnumsAsInts(true)
189 msgs := []*descriptor.Message{}
190 for _, msgdesc := range test.MsgDescs {
191 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
192 }
193 file := descriptor.File{
194 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
195 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
196 Name: proto.String("example.proto"),
197 Package: proto.String("example"),
198 Dependency: []string{},
199 MessageType: test.MsgDescs,
200 Service: []*protodescriptor.ServiceDescriptorProto{},
201 },
202 GoPkg: descriptor.GoPackage{
203 Path: "example.com/path/to/example/example.pb",
204 Name: "example_pb",
205 },
206 Messages: msgs,
207 }
208 reg.Load(&plugin.CodeGeneratorRequest{
209 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
210 })
211
212 message, err := reg.LookupMsg("", ".example."+test.Message)
213 if err != nil {
214 t.Fatalf("failed to lookup message: %s", err)
215 }
216 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
217 if err != nil {
218 t.Fatalf("failed to convert message to query parameters: %s", err)
219 }
220
221 for i := range params {
222 params[i].Items = nil
223 }
224 if !reflect.DeepEqual(params, test.Params) {
225 t.Errorf("expected %v, got %v", test.Params, params)
226 }
227 }
228 }
229
230 func TestMessageToQueryParameters(t *testing.T) {
231 type test struct {
232 MsgDescs []*protodescriptor.DescriptorProto
233 Message string
234 Params []swaggerParameterObject
235 }
236
237 tests := []test{
238 {
239 MsgDescs: []*protodescriptor.DescriptorProto{
240 &protodescriptor.DescriptorProto{
241 Name: proto.String("ExampleMessage"),
242 Field: []*protodescriptor.FieldDescriptorProto{
243 {
244 Name: proto.String("a"),
245 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
246 Number: proto.Int32(1),
247 },
248 {
249 Name: proto.String("b"),
250 Type: protodescriptor.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
251 Number: proto.Int32(2),
252 },
253 {
254 Name: proto.String("c"),
255 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
256 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
257 Number: proto.Int32(3),
258 },
259 },
260 },
261 },
262 Message: "ExampleMessage",
263 Params: []swaggerParameterObject{
264 swaggerParameterObject{
265 Name: "a",
266 In: "query",
267 Required: false,
268 Type: "string",
269 },
270 swaggerParameterObject{
271 Name: "b",
272 In: "query",
273 Required: false,
274 Type: "number",
275 Format: "double",
276 },
277 swaggerParameterObject{
278 Name: "c",
279 In: "query",
280 Required: false,
281 Type: "array",
282 CollectionFormat: "multi",
283 },
284 },
285 },
286 {
287 MsgDescs: []*protodescriptor.DescriptorProto{
288 &protodescriptor.DescriptorProto{
289 Name: proto.String("ExampleMessage"),
290 Field: []*protodescriptor.FieldDescriptorProto{
291 {
292 Name: proto.String("nested"),
293 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
294 TypeName: proto.String(".example.Nested"),
295 Number: proto.Int32(1),
296 },
297 },
298 },
299 &protodescriptor.DescriptorProto{
300 Name: proto.String("Nested"),
301 Field: []*protodescriptor.FieldDescriptorProto{
302 {
303 Name: proto.String("a"),
304 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
305 Number: proto.Int32(1),
306 },
307 {
308 Name: proto.String("deep"),
309 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
310 TypeName: proto.String(".example.Nested.DeepNested"),
311 Number: proto.Int32(2),
312 },
313 },
314 NestedType: []*protodescriptor.DescriptorProto{{
315 Name: proto.String("DeepNested"),
316 Field: []*protodescriptor.FieldDescriptorProto{
317 {
318 Name: proto.String("b"),
319 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
320 Number: proto.Int32(1),
321 },
322 {
323 Name: proto.String("c"),
324 Type: protodescriptor.FieldDescriptorProto_TYPE_ENUM.Enum(),
325 TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
326 Number: proto.Int32(2),
327 },
328 },
329 EnumType: []*protodescriptor.EnumDescriptorProto{
330 {
331 Name: proto.String("DeepEnum"),
332 Value: []*protodescriptor.EnumValueDescriptorProto{
333 {Name: proto.String("FALSE"), Number: proto.Int32(0)},
334 {Name: proto.String("TRUE"), Number: proto.Int32(1)},
335 },
336 },
337 },
338 }},
339 },
340 },
341 Message: "ExampleMessage",
342 Params: []swaggerParameterObject{
343 swaggerParameterObject{
344 Name: "nested.a",
345 In: "query",
346 Required: false,
347 Type: "string",
348 },
349 swaggerParameterObject{
350 Name: "nested.deep.b",
351 In: "query",
352 Required: false,
353 Type: "string",
354 },
355 swaggerParameterObject{
356 Name: "nested.deep.c",
357 In: "query",
358 Required: false,
359 Type: "string",
360 Enum: []string{"FALSE", "TRUE"},
361 Default: "FALSE",
362 },
363 },
364 },
365 }
366
367 for _, test := range tests {
368 reg := descriptor.NewRegistry()
369 msgs := []*descriptor.Message{}
370 for _, msgdesc := range test.MsgDescs {
371 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
372 }
373 file := descriptor.File{
374 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
375 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
376 Name: proto.String("example.proto"),
377 Package: proto.String("example"),
378 Dependency: []string{},
379 MessageType: test.MsgDescs,
380 Service: []*protodescriptor.ServiceDescriptorProto{},
381 },
382 GoPkg: descriptor.GoPackage{
383 Path: "example.com/path/to/example/example.pb",
384 Name: "example_pb",
385 },
386 Messages: msgs,
387 }
388 reg.Load(&plugin.CodeGeneratorRequest{
389 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
390 })
391
392 message, err := reg.LookupMsg("", ".example."+test.Message)
393 if err != nil {
394 t.Fatalf("failed to lookup message: %s", err)
395 }
396 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
397 if err != nil {
398 t.Fatalf("failed to convert message to query parameters: %s", err)
399 }
400
401 for i := range params {
402 params[i].Items = nil
403 }
404 if !reflect.DeepEqual(params, test.Params) {
405 t.Errorf("expected %v, got %v", test.Params, params)
406 }
407 }
408 }
409
410
411
412 func TestMessageToQueryParametersNoRecursive(t *testing.T) {
413 type test struct {
414 MsgDescs []*protodescriptor.DescriptorProto
415 Message string
416 }
417
418 tests := []test{
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434 {
435 MsgDescs: []*protodescriptor.DescriptorProto{
436 &protodescriptor.DescriptorProto{
437 Name: proto.String("QueryMessage"),
438 Field: []*protodescriptor.FieldDescriptorProto{
439 {
440 Name: proto.String("first"),
441 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
442 TypeName: proto.String(".example.BaseMessage"),
443 Number: proto.Int32(1),
444 },
445 {
446 Name: proto.String("second"),
447 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
448 Number: proto.Int32(2),
449 },
450 },
451 },
452 &protodescriptor.DescriptorProto{
453 Name: proto.String("BaseMessage"),
454 Field: []*protodescriptor.FieldDescriptorProto{
455 {
456 Name: proto.String("first"),
457 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
458 TypeName: proto.String(".example.NonRecursiveMessage"),
459 Number: proto.Int32(1),
460 },
461 {
462 Name: proto.String("second"),
463 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
464 TypeName: proto.String(".example.NonRecursiveMessage"),
465 Number: proto.Int32(2),
466 },
467 },
468 },
469
470 &protodescriptor.DescriptorProto{
471 Name: proto.String("NonRecursiveMessage"),
472 Field: []*protodescriptor.FieldDescriptorProto{
473 {
474 Name: proto.String("field"),
475
476 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
477 Number: proto.Int32(1),
478 },
479 },
480 },
481 },
482 Message: "QueryMessage",
483 },
484 }
485
486 for _, test := range tests {
487 reg := descriptor.NewRegistry()
488 msgs := []*descriptor.Message{}
489 for _, msgdesc := range test.MsgDescs {
490 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
491 }
492 file := descriptor.File{
493 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
494 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
495 Name: proto.String("example.proto"),
496 Package: proto.String("example"),
497 Dependency: []string{},
498 MessageType: test.MsgDescs,
499 Service: []*protodescriptor.ServiceDescriptorProto{},
500 },
501 GoPkg: descriptor.GoPackage{
502 Path: "example.com/path/to/example/example.pb",
503 Name: "example_pb",
504 },
505 Messages: msgs,
506 }
507 reg.Load(&plugin.CodeGeneratorRequest{
508 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
509 })
510
511 message, err := reg.LookupMsg("", ".example."+test.Message)
512 if err != nil {
513 t.Fatalf("failed to lookup message: %s", err)
514 }
515
516 _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
517 if err != nil {
518 t.Fatalf("No recursion error should be thrown: %s", err)
519 }
520 }
521 }
522
523
524
525
526 func TestMessageToQueryParametersRecursive(t *testing.T) {
527 type test struct {
528 MsgDescs []*protodescriptor.DescriptorProto
529 Message string
530 }
531
532 tests := []test{
533
534
535
536
537
538
539 {
540 MsgDescs: []*protodescriptor.DescriptorProto{
541 &protodescriptor.DescriptorProto{
542 Name: proto.String("DirectRecursiveMessage"),
543 Field: []*protodescriptor.FieldDescriptorProto{
544 {
545 Name: proto.String("nested"),
546 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
547 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
548 TypeName: proto.String(".example.DirectRecursiveMessage"),
549 Number: proto.Int32(1),
550 },
551 },
552 },
553 },
554 Message: "DirectRecursiveMessage",
555 },
556
557
558
559
560
561
562 {
563 MsgDescs: []*protodescriptor.DescriptorProto{
564 &protodescriptor.DescriptorProto{
565 Name: proto.String("RootMessage"),
566 Field: []*protodescriptor.FieldDescriptorProto{
567 {
568 Name: proto.String("nested"),
569 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
570 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
571 TypeName: proto.String(".example.NodeMessage"),
572 Number: proto.Int32(1),
573 },
574 },
575 },
576 &protodescriptor.DescriptorProto{
577 Name: proto.String("NodeMessage"),
578 Field: []*protodescriptor.FieldDescriptorProto{
579 {
580 Name: proto.String("nested"),
581 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
582 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
583 TypeName: proto.String(".example.CycleMessage"),
584 Number: proto.Int32(1),
585 },
586 },
587 },
588 &protodescriptor.DescriptorProto{
589 Name: proto.String("CycleMessage"),
590 Field: []*protodescriptor.FieldDescriptorProto{
591 {
592 Name: proto.String("nested"),
593 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
594 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
595 TypeName: proto.String(".example.RootMessage"),
596 Number: proto.Int32(1),
597 },
598 },
599 },
600 },
601 Message: "RootMessage",
602 },
603 }
604
605 for _, test := range tests {
606 reg := descriptor.NewRegistry()
607 msgs := []*descriptor.Message{}
608 for _, msgdesc := range test.MsgDescs {
609 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
610 }
611 file := descriptor.File{
612 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
613 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
614 Name: proto.String("example.proto"),
615 Package: proto.String("example"),
616 Dependency: []string{},
617 MessageType: test.MsgDescs,
618 Service: []*protodescriptor.ServiceDescriptorProto{},
619 },
620 GoPkg: descriptor.GoPackage{
621 Path: "example.com/path/to/example/example.pb",
622 Name: "example_pb",
623 },
624 Messages: msgs,
625 }
626 reg.Load(&plugin.CodeGeneratorRequest{
627 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
628 })
629
630 message, err := reg.LookupMsg("", ".example."+test.Message)
631 if err != nil {
632 t.Fatalf("failed to lookup message: %s", err)
633 }
634 _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
635 if err == nil {
636 t.Fatalf("It should not be allowed to have recursive query parameters")
637 }
638 }
639 }
640
641 func TestMessageToQueryParametersWithJsonName(t *testing.T) {
642 type test struct {
643 MsgDescs []*protodescriptor.DescriptorProto
644 Message string
645 Params []swaggerParameterObject
646 }
647
648 tests := []test{
649 {
650 MsgDescs: []*protodescriptor.DescriptorProto{
651 &protodescriptor.DescriptorProto{
652 Name: proto.String("ExampleMessage"),
653 Field: []*protodescriptor.FieldDescriptorProto{
654 {
655 Name: proto.String("test_field_a"),
656 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
657 Number: proto.Int32(1),
658 JsonName: proto.String("testFieldA"),
659 },
660 },
661 },
662 },
663 Message: "ExampleMessage",
664 Params: []swaggerParameterObject{
665 swaggerParameterObject{
666 Name: "testFieldA",
667 In: "query",
668 Required: false,
669 Type: "string",
670 },
671 },
672 },
673 {
674 MsgDescs: []*protodescriptor.DescriptorProto{
675 &protodescriptor.DescriptorProto{
676 Name: proto.String("SubMessage"),
677 Field: []*protodescriptor.FieldDescriptorProto{
678 {
679 Name: proto.String("test_field_a"),
680 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
681 Number: proto.Int32(1),
682 JsonName: proto.String("testFieldA"),
683 },
684 },
685 },
686 &protodescriptor.DescriptorProto{
687 Name: proto.String("ExampleMessage"),
688 Field: []*protodescriptor.FieldDescriptorProto{
689 {
690 Name: proto.String("sub_message"),
691 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
692 TypeName: proto.String(".example.SubMessage"),
693 Number: proto.Int32(1),
694 JsonName: proto.String("subMessage"),
695 },
696 },
697 },
698 },
699 Message: "ExampleMessage",
700 Params: []swaggerParameterObject{
701 swaggerParameterObject{
702 Name: "subMessage.testFieldA",
703 In: "query",
704 Required: false,
705 Type: "string",
706 },
707 },
708 },
709 }
710
711 for _, test := range tests {
712 reg := descriptor.NewRegistry()
713 reg.SetUseJSONNamesForFields(true)
714 msgs := []*descriptor.Message{}
715 for _, msgdesc := range test.MsgDescs {
716 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
717 }
718 file := descriptor.File{
719 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
720 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
721 Name: proto.String("example.proto"),
722 Package: proto.String("example"),
723 Dependency: []string{},
724 MessageType: test.MsgDescs,
725 Service: []*protodescriptor.ServiceDescriptorProto{},
726 },
727 GoPkg: descriptor.GoPackage{
728 Path: "example.com/path/to/example/example.pb",
729 Name: "example_pb",
730 },
731 Messages: msgs,
732 }
733 reg.Load(&plugin.CodeGeneratorRequest{
734 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
735 })
736
737 message, err := reg.LookupMsg("", ".example."+test.Message)
738 if err != nil {
739 t.Fatalf("failed to lookup message: %s", err)
740 }
741 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil)
742 if err != nil {
743 t.Fatalf("failed to convert message to query parameters: %s", err)
744 }
745 if !reflect.DeepEqual(params, test.Params) {
746 t.Errorf("expected %v, got %v", test.Params, params)
747 }
748 }
749 }
750
751 func TestApplyTemplateSimple(t *testing.T) {
752 msgdesc := &protodescriptor.DescriptorProto{
753 Name: proto.String("ExampleMessage"),
754 }
755 meth := &protodescriptor.MethodDescriptorProto{
756 Name: proto.String("Example"),
757 InputType: proto.String("ExampleMessage"),
758 OutputType: proto.String("ExampleMessage"),
759 }
760 svc := &protodescriptor.ServiceDescriptorProto{
761 Name: proto.String("ExampleService"),
762 Method: []*protodescriptor.MethodDescriptorProto{meth},
763 }
764 msg := &descriptor.Message{
765 DescriptorProto: msgdesc,
766 }
767 file := descriptor.File{
768 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
769 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
770 Name: proto.String("example.proto"),
771 Package: proto.String("example"),
772 Dependency: []string{"a.example/b/c.proto", "a.example/d/e.proto"},
773 MessageType: []*protodescriptor.DescriptorProto{msgdesc},
774 Service: []*protodescriptor.ServiceDescriptorProto{svc},
775 },
776 GoPkg: descriptor.GoPackage{
777 Path: "example.com/path/to/example/example.pb",
778 Name: "example_pb",
779 },
780 Messages: []*descriptor.Message{msg},
781 Services: []*descriptor.Service{
782 {
783 ServiceDescriptorProto: svc,
784 Methods: []*descriptor.Method{
785 {
786 MethodDescriptorProto: meth,
787 RequestType: msg,
788 ResponseType: msg,
789 Bindings: []*descriptor.Binding{
790 {
791 HTTPMethod: "GET",
792 Body: &descriptor.Body{FieldPath: nil},
793 PathTmpl: httprule.Template{
794 Version: 1,
795 OpCodes: []int{0, 0},
796 Template: "/v1/echo",
797 },
798 },
799 },
800 },
801 },
802 },
803 },
804 }
805 reg := descriptor.NewRegistry()
806 fileCL := crossLinkFixture(&file)
807 err := reg.Load(reqFromFile(fileCL))
808 if err != nil {
809 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
810 return
811 }
812 result, err := applyTemplate(param{File: fileCL, reg: reg})
813 if err != nil {
814 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
815 return
816 }
817 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
818 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
819 }
820 if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) {
821 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
822 }
823 if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) {
824 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
825 }
826 if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) {
827 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
828 }
829 if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
830 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
831 }
832
833
834 if t.Failed() {
835 t.Errorf("had: %s", file)
836 t.Errorf("got: %s", fmt.Sprint(result))
837 }
838 }
839
840 func TestApplyTemplateMultiService(t *testing.T) {
841 msgdesc := &protodescriptor.DescriptorProto{
842 Name: proto.String("ExampleMessage"),
843 }
844 meth := &protodescriptor.MethodDescriptorProto{
845 Name: proto.String("Example"),
846 InputType: proto.String("ExampleMessage"),
847 OutputType: proto.String("ExampleMessage"),
848 }
849
850
851
852 svc := &protodescriptor.ServiceDescriptorProto{
853 Name: proto.String("ExampleService"),
854 Method: []*protodescriptor.MethodDescriptorProto{meth},
855 }
856 svc2 := &protodescriptor.ServiceDescriptorProto{
857 Name: proto.String("OtherService"),
858 Method: []*protodescriptor.MethodDescriptorProto{meth},
859 }
860
861 msg := &descriptor.Message{
862 DescriptorProto: msgdesc,
863 }
864 file := descriptor.File{
865 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
866 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
867 Name: proto.String("example.proto"),
868 Package: proto.String("example"),
869 Dependency: []string{"a.example/b/c.proto", "a.example/d/e.proto"},
870 MessageType: []*protodescriptor.DescriptorProto{msgdesc},
871 Service: []*protodescriptor.ServiceDescriptorProto{svc},
872 },
873 GoPkg: descriptor.GoPackage{
874 Path: "example.com/path/to/example/example.pb",
875 Name: "example_pb",
876 },
877 Messages: []*descriptor.Message{msg},
878 Services: []*descriptor.Service{
879 {
880 ServiceDescriptorProto: svc,
881 Methods: []*descriptor.Method{
882 {
883 MethodDescriptorProto: meth,
884 RequestType: msg,
885 ResponseType: msg,
886 Bindings: []*descriptor.Binding{
887 {
888 HTTPMethod: "GET",
889 Body: &descriptor.Body{FieldPath: nil},
890 PathTmpl: httprule.Template{
891 Version: 1,
892 OpCodes: []int{0, 0},
893 Template: "/v1/echo",
894 },
895 },
896 },
897 },
898 },
899 },
900 {
901 ServiceDescriptorProto: svc2,
902 Methods: []*descriptor.Method{
903 {
904 MethodDescriptorProto: meth,
905 RequestType: msg,
906 ResponseType: msg,
907 Bindings: []*descriptor.Binding{
908 {
909 HTTPMethod: "GET",
910 Body: &descriptor.Body{FieldPath: nil},
911 PathTmpl: httprule.Template{
912 Version: 1,
913 OpCodes: []int{0, 0},
914 Template: "/v1/ping",
915 },
916 },
917 },
918 },
919 },
920 },
921 },
922 }
923 reg := descriptor.NewRegistry()
924 fileCL := crossLinkFixture(&file)
925 err := reg.Load(reqFromFile(fileCL))
926 if err != nil {
927 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
928 return
929 }
930 result, err := applyTemplate(param{File: fileCL, reg: reg})
931 if err != nil {
932 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
933 return
934 }
935
936
937
938 if want, is := "ExampleService_Example", result.Paths["/v1/echo"].Get.OperationID; !reflect.DeepEqual(is, want) {
939 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
940 }
941 if want, is := "OtherService_Example", result.Paths["/v1/ping"].Get.OperationID; !reflect.DeepEqual(is, want) {
942 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
943 }
944
945
946 if t.Failed() {
947 t.Errorf("had: %s", file)
948 t.Errorf("got: %s", fmt.Sprint(result))
949 }
950 }
951
952 func TestApplyTemplateOverrideOperationID(t *testing.T) {
953 msgdesc := &protodescriptor.DescriptorProto{
954 Name: proto.String("ExampleMessage"),
955 }
956 meth := &protodescriptor.MethodDescriptorProto{
957 Name: proto.String("Example"),
958 InputType: proto.String("ExampleMessage"),
959 OutputType: proto.String("ExampleMessage"),
960 Options: &protodescriptor.MethodOptions{},
961 }
962 swaggerOperation := swagger_options.Operation{
963 OperationId: "MyExample",
964 }
965 if err := proto.SetExtension(proto.Message(meth.Options), swagger_options.E_Openapiv2Operation, &swaggerOperation); err != nil {
966 t.Fatalf("proto.SetExtension(MethodDescriptorProto.Options) failed: %v", err)
967 }
968
969 svc := &protodescriptor.ServiceDescriptorProto{
970 Name: proto.String("ExampleService"),
971 Method: []*protodescriptor.MethodDescriptorProto{meth},
972 }
973 msg := &descriptor.Message{
974 DescriptorProto: msgdesc,
975 }
976 file := descriptor.File{
977 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
978 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
979 Name: proto.String("example.proto"),
980 Package: proto.String("example"),
981 Dependency: []string{"a.example/b/c.proto", "a.example/d/e.proto"},
982 MessageType: []*protodescriptor.DescriptorProto{msgdesc},
983 Service: []*protodescriptor.ServiceDescriptorProto{svc},
984 },
985 GoPkg: descriptor.GoPackage{
986 Path: "example.com/path/to/example/example.pb",
987 Name: "example_pb",
988 },
989 Messages: []*descriptor.Message{msg},
990 Services: []*descriptor.Service{
991 {
992 ServiceDescriptorProto: svc,
993 Methods: []*descriptor.Method{
994 {
995 MethodDescriptorProto: meth,
996 RequestType: msg,
997 ResponseType: msg,
998 Bindings: []*descriptor.Binding{
999 {
1000 HTTPMethod: "GET",
1001 Body: &descriptor.Body{FieldPath: nil},
1002 PathTmpl: httprule.Template{
1003 Version: 1,
1004 OpCodes: []int{0, 0},
1005 Template: "/v1/echo",
1006 },
1007 },
1008 },
1009 },
1010 },
1011 },
1012 },
1013 }
1014
1015 reg := descriptor.NewRegistry()
1016 fileCL := crossLinkFixture(&file)
1017 err := reg.Load(reqFromFile(fileCL))
1018 if err != nil {
1019 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
1020 return
1021 }
1022 result, err := applyTemplate(param{File: fileCL, reg: reg})
1023 if err != nil {
1024 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
1025 return
1026 }
1027 if want, is := "MyExample", result.Paths["/v1/echo"].Get.OperationID; !reflect.DeepEqual(is, want) {
1028 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
1029 }
1030
1031
1032 if t.Failed() {
1033 t.Errorf("had: %s", file)
1034 t.Errorf("got: %s", fmt.Sprint(result))
1035 }
1036 }
1037
1038 func TestApplyTemplateExtensions(t *testing.T) {
1039 msgdesc := &protodescriptor.DescriptorProto{
1040 Name: proto.String("ExampleMessage"),
1041 }
1042 meth := &protodescriptor.MethodDescriptorProto{
1043 Name: proto.String("Example"),
1044 InputType: proto.String("ExampleMessage"),
1045 OutputType: proto.String("ExampleMessage"),
1046 Options: &protodescriptor.MethodOptions{},
1047 }
1048 svc := &protodescriptor.ServiceDescriptorProto{
1049 Name: proto.String("ExampleService"),
1050 Method: []*protodescriptor.MethodDescriptorProto{meth},
1051 }
1052 msg := &descriptor.Message{
1053 DescriptorProto: msgdesc,
1054 }
1055 file := descriptor.File{
1056 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
1057 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
1058 Name: proto.String("example.proto"),
1059 Package: proto.String("example"),
1060 Dependency: []string{"a.example/b/c.proto", "a.example/d/e.proto"},
1061 MessageType: []*protodescriptor.DescriptorProto{msgdesc},
1062 Service: []*protodescriptor.ServiceDescriptorProto{svc},
1063 Options: &protodescriptor.FileOptions{},
1064 },
1065 GoPkg: descriptor.GoPackage{
1066 Path: "example.com/path/to/example/example.pb",
1067 Name: "example_pb",
1068 },
1069 Messages: []*descriptor.Message{msg},
1070 Services: []*descriptor.Service{
1071 {
1072 ServiceDescriptorProto: svc,
1073 Methods: []*descriptor.Method{
1074 {
1075 MethodDescriptorProto: meth,
1076 RequestType: msg,
1077 ResponseType: msg,
1078 Bindings: []*descriptor.Binding{
1079 {
1080 HTTPMethod: "GET",
1081 Body: &descriptor.Body{FieldPath: nil},
1082 PathTmpl: httprule.Template{
1083 Version: 1,
1084 OpCodes: []int{0, 0},
1085 Template: "/v1/echo",
1086 },
1087 },
1088 },
1089 },
1090 },
1091 },
1092 },
1093 }
1094 swagger := swagger_options.Swagger{
1095 Info: &swagger_options.Info{
1096 Title: "test",
1097 Extensions: map[string]*structpb.Value{
1098 "x-info-extension": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "bar"}},
1099 },
1100 },
1101 Extensions: map[string]*structpb.Value{
1102 "x-foo": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "bar"}},
1103 "x-bar": &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
1104 Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "baz"}}},
1105 }}},
1106 },
1107 SecurityDefinitions: &swagger_options.SecurityDefinitions{
1108 Security: map[string]*swagger_options.SecurityScheme{
1109 "somescheme": &swagger_options.SecurityScheme{
1110 Extensions: map[string]*structpb.Value{
1111 "x-security-baz": &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: true}},
1112 },
1113 },
1114 },
1115 },
1116 }
1117 if err := proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), swagger_options.E_Openapiv2Swagger, &swagger); err != nil {
1118 t.Fatalf("proto.SetExtension(FileDescriptorProto.Options) failed: %v", err)
1119 }
1120
1121 swaggerOperation := swagger_options.Operation{
1122 Responses: map[string]*swagger_options.Response{
1123 "200": &swagger_options.Response{
1124 Extensions: map[string]*structpb.Value{
1125 "x-resp-id": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "resp1000"}},
1126 },
1127 },
1128 },
1129 Extensions: map[string]*structpb.Value{
1130 "x-op-foo": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "baz"}},
1131 },
1132 }
1133 if err := proto.SetExtension(proto.Message(meth.Options), swagger_options.E_Openapiv2Operation, &swaggerOperation); err != nil {
1134 t.Fatalf("proto.SetExtension(MethodDescriptorProto.Options) failed: %v", err)
1135 }
1136 reg := descriptor.NewRegistry()
1137 fileCL := crossLinkFixture(&file)
1138 err := reg.Load(reqFromFile(fileCL))
1139 if err != nil {
1140 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
1141 return
1142 }
1143 result, err := applyTemplate(param{File: fileCL, reg: reg})
1144 if err != nil {
1145 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
1146 return
1147 }
1148 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
1149 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1150 }
1151 if want, is, name := []extension{
1152 {key: "x-bar", value: json.RawMessage("[\n \"baz\"\n ]")},
1153 {key: "x-foo", value: json.RawMessage("\"bar\"")},
1154 }, result.extensions, "Extensions"; !reflect.DeepEqual(is, want) {
1155 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1156 }
1157
1158 var scheme swaggerSecuritySchemeObject
1159 for _, v := range result.SecurityDefinitions {
1160 scheme = v
1161 }
1162 if want, is, name := []extension{
1163 {key: "x-security-baz", value: json.RawMessage("true")},
1164 }, scheme.extensions, "SecurityScheme.Extensions"; !reflect.DeepEqual(is, want) {
1165 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1166 }
1167
1168 if want, is, name := []extension{
1169 {key: "x-info-extension", value: json.RawMessage("\"bar\"")},
1170 }, result.Info.extensions, "Info.Extensions"; !reflect.DeepEqual(is, want) {
1171 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1172 }
1173
1174 var operation *swaggerOperationObject
1175 var response swaggerResponseObject
1176 for _, v := range result.Paths {
1177 operation = v.Get
1178 response = v.Get.Responses["200"]
1179 }
1180 if want, is, name := []extension{
1181 {key: "x-op-foo", value: json.RawMessage("\"baz\"")},
1182 }, operation.extensions, "operation.Extensions"; !reflect.DeepEqual(is, want) {
1183 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1184 }
1185 if want, is, name := []extension{
1186 {key: "x-resp-id", value: json.RawMessage("\"resp1000\"")},
1187 }, response.extensions, "response.Extensions"; !reflect.DeepEqual(is, want) {
1188 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1189 }
1190 }
1191
1192 func TestValidateHeaderType(t *testing.T) {
1193 type test struct {
1194 Type string
1195 Format string
1196 expectedError error
1197 }
1198 tests := []test{
1199 {
1200 "string",
1201 "date-time",
1202 nil,
1203 },
1204 {
1205 "boolean",
1206 "",
1207 nil,
1208 },
1209 {
1210 "integer",
1211 "uint",
1212 nil,
1213 },
1214 {
1215 "integer",
1216 "uint8",
1217 nil,
1218 },
1219 {
1220 "integer",
1221 "uint16",
1222 nil,
1223 },
1224 {
1225 "integer",
1226 "uint32",
1227 nil,
1228 },
1229 {
1230 "integer",
1231 "uint64",
1232 nil,
1233 },
1234 {
1235 "integer",
1236 "int",
1237 nil,
1238 },
1239 {
1240 "integer",
1241 "int8",
1242 nil,
1243 },
1244 {
1245 "integer",
1246 "int16",
1247 nil,
1248 },
1249 {
1250 "integer",
1251 "int32",
1252 nil,
1253 },
1254 {
1255 "integer",
1256 "int64",
1257 nil,
1258 },
1259 {
1260 "integer",
1261 "float64",
1262 errors.New("the provided format \"float64\" is not a valid extension of the type \"integer\""),
1263 },
1264 {
1265 "integer",
1266 "uuid",
1267 errors.New("the provided format \"uuid\" is not a valid extension of the type \"integer\""),
1268 },
1269 {
1270 "number",
1271 "uint",
1272 nil,
1273 },
1274 {
1275 "number",
1276 "uint8",
1277 nil,
1278 },
1279 {
1280 "number",
1281 "uint16",
1282 nil,
1283 },
1284 {
1285 "number",
1286 "uint32",
1287 nil,
1288 },
1289 {
1290 "number",
1291 "uint64",
1292 nil,
1293 },
1294 {
1295 "number",
1296 "int",
1297 nil,
1298 },
1299 {
1300 "number",
1301 "int8",
1302 nil,
1303 },
1304 {
1305 "number",
1306 "int16",
1307 nil,
1308 },
1309 {
1310 "number",
1311 "int32",
1312 nil,
1313 },
1314 {
1315 "number",
1316 "int64",
1317 nil,
1318 },
1319 {
1320 "number",
1321 "float",
1322 nil,
1323 },
1324 {
1325 "number",
1326 "float32",
1327 nil,
1328 },
1329 {
1330 "number",
1331 "float64",
1332 nil,
1333 },
1334 {
1335 "number",
1336 "complex64",
1337 nil,
1338 },
1339 {
1340 "number",
1341 "complex128",
1342 nil,
1343 },
1344 {
1345 "number",
1346 "double",
1347 nil,
1348 },
1349 {
1350 "number",
1351 "byte",
1352 nil,
1353 },
1354 {
1355 "number",
1356 "rune",
1357 nil,
1358 },
1359 {
1360 "number",
1361 "uintptr",
1362 nil,
1363 },
1364 {
1365 "number",
1366 "date",
1367 errors.New("the provided format \"date\" is not a valid extension of the type \"number\""),
1368 },
1369 {
1370 "array",
1371 "",
1372 errors.New("the provided header type \"array\" is not supported"),
1373 },
1374 {
1375 "foo",
1376 "",
1377 errors.New("the provided header type \"foo\" is not supported"),
1378 },
1379 }
1380 for _, v := range tests {
1381 err := validateHeaderTypeAndFormat(v.Type, v.Format)
1382
1383 if v.expectedError == nil {
1384 if err != nil {
1385 t.Errorf("unexpected error %v", err)
1386 }
1387 } else {
1388 if err == nil {
1389 t.Fatal("expected header error not returned")
1390 }
1391 if err.Error() != v.expectedError.Error() {
1392 t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
1393 }
1394 }
1395 }
1396
1397 }
1398
1399 func TestValidateDefaultValueType(t *testing.T) {
1400 type test struct {
1401 Type string
1402 Value string
1403 Format string
1404 expectedError error
1405 }
1406 tests := []test{
1407 {
1408 "string",
1409 `"string"`,
1410 "",
1411 nil,
1412 },
1413 {
1414 "string",
1415 "\"2012-11-01T22:08:41+00:00\"",
1416 "date-time",
1417 nil,
1418 },
1419 {
1420 "string",
1421 "\"2012-11-01\"",
1422 "date",
1423 nil,
1424 },
1425 {
1426 "string",
1427 "0",
1428 "",
1429 errors.New("the provided default value \"0\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
1430 },
1431 {
1432 "string",
1433 "false",
1434 "",
1435 errors.New("the provided default value \"false\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
1436 },
1437 {
1438 "boolean",
1439 "true",
1440 "",
1441 nil,
1442 },
1443 {
1444 "boolean",
1445 "0",
1446 "",
1447 errors.New("the provided default value \"0\" does not match provider type \"boolean\""),
1448 },
1449 {
1450 "boolean",
1451 `"string"`,
1452 "",
1453 errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"boolean\""),
1454 },
1455 {
1456 "number",
1457 "1.2",
1458 "",
1459 nil,
1460 },
1461 {
1462 "number",
1463 "123",
1464 "",
1465 nil,
1466 },
1467 {
1468 "number",
1469 "nan",
1470 "",
1471 errors.New("the provided number \"nan\" is not a valid JSON number"),
1472 },
1473 {
1474 "number",
1475 "NaN",
1476 "",
1477 errors.New("the provided number \"NaN\" is not a valid JSON number"),
1478 },
1479 {
1480 "number",
1481 "-459.67",
1482 "",
1483 nil,
1484 },
1485 {
1486 "number",
1487 "inf",
1488 "",
1489 errors.New("the provided number \"inf\" is not a valid JSON number"),
1490 },
1491 {
1492 "number",
1493 "infinity",
1494 "",
1495 errors.New("the provided number \"infinity\" is not a valid JSON number"),
1496 },
1497 {
1498 "number",
1499 "Inf",
1500 "",
1501 errors.New("the provided number \"Inf\" is not a valid JSON number"),
1502 },
1503 {
1504 "number",
1505 "Infinity",
1506 "",
1507 errors.New("the provided number \"Infinity\" is not a valid JSON number"),
1508 },
1509 {
1510 "number",
1511 "false",
1512 "",
1513 errors.New("the provided default value \"false\" does not match provider type \"number\""),
1514 },
1515 {
1516 "number",
1517 `"string"`,
1518 "",
1519 errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"number\""),
1520 },
1521 {
1522 "integer",
1523 "2",
1524 "",
1525 nil,
1526 },
1527 {
1528 "integer",
1529 fmt.Sprint(math.MaxInt32),
1530 "int32",
1531 nil,
1532 },
1533 {
1534 "integer",
1535 fmt.Sprint(math.MaxInt32 + 1),
1536 "int32",
1537 errors.New("the provided default value \"2147483648\" does not match provided format \"int32\""),
1538 },
1539 {
1540 "integer",
1541 fmt.Sprint(math.MaxInt64),
1542 "int64",
1543 nil,
1544 },
1545 {
1546 "integer",
1547 "9223372036854775808",
1548 "int64",
1549 errors.New("the provided default value \"9223372036854775808\" does not match provided format \"int64\""),
1550 },
1551 {
1552 "integer",
1553 "18446744073709551615",
1554 "uint64",
1555 nil,
1556 },
1557 {
1558 "integer",
1559 "false",
1560 "",
1561 errors.New("the provided default value \"false\" does not match provided type \"integer\""),
1562 },
1563 {
1564 "integer",
1565 "1.2",
1566 "",
1567 errors.New("the provided default value \"1.2\" does not match provided type \"integer\""),
1568 },
1569 {
1570 "integer",
1571 `"string"`,
1572 "",
1573 errors.New("the provided default value \"\\\"string\\\"\" does not match provided type \"integer\""),
1574 },
1575 }
1576 for _, v := range tests {
1577 err := validateDefaultValueTypeAndFormat(v.Type, v.Value, v.Format)
1578
1579 if v.expectedError == nil {
1580 if err != nil {
1581 t.Errorf("unexpected error '%v'", err)
1582 }
1583 } else {
1584 if err == nil {
1585 t.Error("expected update error not returned")
1586 }
1587 if err.Error() != v.expectedError.Error() {
1588 t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
1589 }
1590 }
1591 }
1592
1593 }
1594
1595 func TestApplyTemplateHeaders(t *testing.T) {
1596 msgdesc := &protodescriptor.DescriptorProto{
1597 Name: proto.String("ExampleMessage"),
1598 }
1599 meth := &protodescriptor.MethodDescriptorProto{
1600 Name: proto.String("Example"),
1601 InputType: proto.String("ExampleMessage"),
1602 OutputType: proto.String("ExampleMessage"),
1603 Options: &protodescriptor.MethodOptions{},
1604 }
1605 svc := &protodescriptor.ServiceDescriptorProto{
1606 Name: proto.String("ExampleService"),
1607 Method: []*protodescriptor.MethodDescriptorProto{meth},
1608 }
1609 msg := &descriptor.Message{
1610 DescriptorProto: msgdesc,
1611 }
1612 file := descriptor.File{
1613 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
1614 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
1615 Name: proto.String("example.proto"),
1616 Package: proto.String("example"),
1617 Dependency: []string{"a.example/b/c.proto", "a.example/d/e.proto"},
1618 MessageType: []*protodescriptor.DescriptorProto{msgdesc},
1619 Service: []*protodescriptor.ServiceDescriptorProto{svc},
1620 Options: &protodescriptor.FileOptions{},
1621 },
1622 GoPkg: descriptor.GoPackage{
1623 Path: "example.com/path/to/example/example.pb",
1624 Name: "example_pb",
1625 },
1626 Messages: []*descriptor.Message{msg},
1627 Services: []*descriptor.Service{
1628 {
1629 ServiceDescriptorProto: svc,
1630 Methods: []*descriptor.Method{
1631 {
1632 MethodDescriptorProto: meth,
1633 RequestType: msg,
1634 ResponseType: msg,
1635 Bindings: []*descriptor.Binding{
1636 {
1637 HTTPMethod: "GET",
1638 Body: &descriptor.Body{FieldPath: nil},
1639 PathTmpl: httprule.Template{
1640 Version: 1,
1641 OpCodes: []int{0, 0},
1642 Template: "/v1/echo",
1643 },
1644 },
1645 },
1646 },
1647 },
1648 },
1649 },
1650 }
1651
1652 swaggerOperation := swagger_options.Operation{
1653 Responses: map[string]*swagger_options.Response{
1654 "200": &swagger_options.Response{
1655 Description: "Testing Headers",
1656 Headers: map[string]*swagger_options.Header{
1657 "string": {
1658 Description: "string header description",
1659 Type: "string",
1660 Format: "uuid",
1661 Pattern: "",
1662 },
1663 "boolean": {
1664 Description: "boolean header description",
1665 Type: "boolean",
1666 Default: "true",
1667 Pattern: "^true|false$",
1668 },
1669 "integer": {
1670 Description: "integer header description",
1671 Type: "integer",
1672 Default: "0",
1673 Pattern: "^[0-9]$",
1674 },
1675 "number": {
1676 Description: "number header description",
1677 Type: "number",
1678 Default: "1.2",
1679 Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
1680 },
1681 },
1682 },
1683 },
1684 }
1685 if err := proto.SetExtension(proto.Message(meth.Options), swagger_options.E_Openapiv2Operation, &swaggerOperation); err != nil {
1686 t.Fatalf("proto.SetExtension(MethodDescriptorProto.Options) failed: %v", err)
1687 }
1688 reg := descriptor.NewRegistry()
1689 fileCL := crossLinkFixture(&file)
1690 err := reg.Load(reqFromFile(fileCL))
1691 if err != nil {
1692 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
1693 return
1694 }
1695 result, err := applyTemplate(param{File: fileCL, reg: reg})
1696 if err != nil {
1697 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
1698 return
1699 }
1700 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
1701 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1702 }
1703
1704 var response swaggerResponseObject
1705 for _, v := range result.Paths {
1706 response = v.Get.Responses["200"]
1707 }
1708 if want, is, name := []swaggerHeadersObject{
1709 {
1710 "String": swaggerHeaderObject{
1711 Description: "string header description",
1712 Type: "string",
1713 Format: "uuid",
1714 Pattern: "",
1715 },
1716 "Boolean": swaggerHeaderObject{
1717 Description: "boolean header description",
1718 Type: "boolean",
1719 Default: json.RawMessage("true"),
1720 Pattern: "^true|false$",
1721 },
1722 "Integer": swaggerHeaderObject{
1723 Description: "integer header description",
1724 Type: "integer",
1725 Default: json.RawMessage("0"),
1726 Pattern: "^[0-9]$",
1727 },
1728 "Number": swaggerHeaderObject{
1729 Description: "number header description",
1730 Type: "number",
1731 Default: json.RawMessage("1.2"),
1732 Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
1733 },
1734 },
1735 }[0], response.Headers, "response.Headers"; !reflect.DeepEqual(is, want) {
1736 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
1737 }
1738 }
1739
1740 func TestApplyTemplateRequestWithoutClientStreaming(t *testing.T) {
1741 msgdesc := &protodescriptor.DescriptorProto{
1742 Name: proto.String("ExampleMessage"),
1743 Field: []*protodescriptor.FieldDescriptorProto{
1744 {
1745 Name: proto.String("nested"),
1746 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1747 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
1748 TypeName: proto.String("NestedMessage"),
1749 Number: proto.Int32(1),
1750 },
1751 },
1752 }
1753 nesteddesc := &protodescriptor.DescriptorProto{
1754 Name: proto.String("NestedMessage"),
1755 Field: []*protodescriptor.FieldDescriptorProto{
1756 {
1757 Name: proto.String("int32"),
1758 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1759 Type: protodescriptor.FieldDescriptorProto_TYPE_INT32.Enum(),
1760 Number: proto.Int32(1),
1761 },
1762 {
1763 Name: proto.String("bool"),
1764 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1765 Type: protodescriptor.FieldDescriptorProto_TYPE_BOOL.Enum(),
1766 Number: proto.Int32(2),
1767 },
1768 },
1769 }
1770 meth := &protodescriptor.MethodDescriptorProto{
1771 Name: proto.String("Echo"),
1772 InputType: proto.String("ExampleMessage"),
1773 OutputType: proto.String("ExampleMessage"),
1774 ClientStreaming: proto.Bool(false),
1775 }
1776 svc := &protodescriptor.ServiceDescriptorProto{
1777 Name: proto.String("ExampleService"),
1778 Method: []*protodescriptor.MethodDescriptorProto{meth},
1779 }
1780
1781 meth.ServerStreaming = proto.Bool(false)
1782
1783 msg := &descriptor.Message{
1784 DescriptorProto: msgdesc,
1785 }
1786 nested := &descriptor.Message{
1787 DescriptorProto: nesteddesc,
1788 }
1789
1790 nestedField := &descriptor.Field{
1791 Message: msg,
1792 FieldDescriptorProto: msg.GetField()[0],
1793 }
1794 intField := &descriptor.Field{
1795 Message: nested,
1796 FieldDescriptorProto: nested.GetField()[0],
1797 }
1798 boolField := &descriptor.Field{
1799 Message: nested,
1800 FieldDescriptorProto: nested.GetField()[1],
1801 }
1802 file := descriptor.File{
1803 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
1804 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
1805 Name: proto.String("example.proto"),
1806 Package: proto.String("example"),
1807 MessageType: []*protodescriptor.DescriptorProto{msgdesc, nesteddesc},
1808 Service: []*protodescriptor.ServiceDescriptorProto{svc},
1809 },
1810 GoPkg: descriptor.GoPackage{
1811 Path: "example.com/path/to/example/example.pb",
1812 Name: "example_pb",
1813 },
1814 Messages: []*descriptor.Message{msg, nested},
1815 Services: []*descriptor.Service{
1816 {
1817 ServiceDescriptorProto: svc,
1818 Methods: []*descriptor.Method{
1819 {
1820 MethodDescriptorProto: meth,
1821 RequestType: msg,
1822 ResponseType: msg,
1823 Bindings: []*descriptor.Binding{
1824 {
1825 HTTPMethod: "POST",
1826 PathTmpl: httprule.Template{
1827 Version: 1,
1828 OpCodes: []int{0, 0},
1829 Template: "/v1/echo",
1830 },
1831 PathParams: []descriptor.Parameter{
1832 {
1833 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
1834 {
1835 Name: "nested",
1836 Target: nestedField,
1837 },
1838 {
1839 Name: "int32",
1840 Target: intField,
1841 },
1842 }),
1843 Target: intField,
1844 },
1845 },
1846 Body: &descriptor.Body{
1847 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
1848 {
1849 Name: "nested",
1850 Target: nestedField,
1851 },
1852 {
1853 Name: "bool",
1854 Target: boolField,
1855 },
1856 }),
1857 },
1858 },
1859 },
1860 },
1861 },
1862 },
1863 },
1864 }
1865 reg := descriptor.NewRegistry()
1866 reg.Load(&plugin.CodeGeneratorRequest{ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto}})
1867 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
1868 if err != nil {
1869 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
1870 return
1871 }
1872 if want, got := "2.0", result.Swagger; !reflect.DeepEqual(got, want) {
1873 t.Errorf("applyTemplate(%#v).Swagger = %s want to be %s", file, got, want)
1874 }
1875 if want, got := "", result.BasePath; !reflect.DeepEqual(got, want) {
1876 t.Errorf("applyTemplate(%#v).BasePath = %s want to be %s", file, got, want)
1877 }
1878 if want, got := ([]string)(nil), result.Schemes; !reflect.DeepEqual(got, want) {
1879 t.Errorf("applyTemplate(%#v).Schemes = %s want to be %s", file, got, want)
1880 }
1881 if want, got := []string{"application/json"}, result.Consumes; !reflect.DeepEqual(got, want) {
1882 t.Errorf("applyTemplate(%#v).Consumes = %s want to be %s", file, got, want)
1883 }
1884 if want, got := []string{"application/json"}, result.Produces; !reflect.DeepEqual(got, want) {
1885 t.Errorf("applyTemplate(%#v).Produces = %s want to be %s", file, got, want)
1886 }
1887
1888
1889 if t.Failed() {
1890 t.Errorf("had: %s", file)
1891 t.Errorf("got: %s", fmt.Sprint(result))
1892 }
1893 }
1894
1895 func TestApplyTemplateRequestWithClientStreaming(t *testing.T) {
1896 msgdesc := &protodescriptor.DescriptorProto{
1897 Name: proto.String("ExampleMessage"),
1898 Field: []*protodescriptor.FieldDescriptorProto{
1899 {
1900 Name: proto.String("nested"),
1901 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1902 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
1903 TypeName: proto.String("NestedMessage"),
1904 Number: proto.Int32(1),
1905 },
1906 },
1907 }
1908 nesteddesc := &protodescriptor.DescriptorProto{
1909 Name: proto.String("NestedMessage"),
1910 Field: []*protodescriptor.FieldDescriptorProto{
1911 {
1912 Name: proto.String("int32"),
1913 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1914 Type: protodescriptor.FieldDescriptorProto_TYPE_INT32.Enum(),
1915 Number: proto.Int32(1),
1916 },
1917 {
1918 Name: proto.String("bool"),
1919 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
1920 Type: protodescriptor.FieldDescriptorProto_TYPE_BOOL.Enum(),
1921 Number: proto.Int32(2),
1922 },
1923 },
1924 }
1925 meth := &protodescriptor.MethodDescriptorProto{
1926 Name: proto.String("Echo"),
1927 InputType: proto.String("ExampleMessage"),
1928 OutputType: proto.String("ExampleMessage"),
1929 ClientStreaming: proto.Bool(true),
1930 ServerStreaming: proto.Bool(true),
1931 }
1932 svc := &protodescriptor.ServiceDescriptorProto{
1933 Name: proto.String("ExampleService"),
1934 Method: []*protodescriptor.MethodDescriptorProto{meth},
1935 }
1936
1937 msg := &descriptor.Message{
1938 DescriptorProto: msgdesc,
1939 }
1940 nested := &descriptor.Message{
1941 DescriptorProto: nesteddesc,
1942 }
1943
1944 nestedField := &descriptor.Field{
1945 Message: msg,
1946 FieldDescriptorProto: msg.GetField()[0],
1947 }
1948 intField := &descriptor.Field{
1949 Message: nested,
1950 FieldDescriptorProto: nested.GetField()[0],
1951 }
1952 boolField := &descriptor.Field{
1953 Message: nested,
1954 FieldDescriptorProto: nested.GetField()[1],
1955 }
1956 file := descriptor.File{
1957 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
1958 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
1959 Name: proto.String("example.proto"),
1960 Package: proto.String("example"),
1961 MessageType: []*protodescriptor.DescriptorProto{msgdesc, nesteddesc},
1962 Service: []*protodescriptor.ServiceDescriptorProto{svc},
1963 },
1964 GoPkg: descriptor.GoPackage{
1965 Path: "example.com/path/to/example/example.pb",
1966 Name: "example_pb",
1967 },
1968 Messages: []*descriptor.Message{msg, nested},
1969 Services: []*descriptor.Service{
1970 {
1971 ServiceDescriptorProto: svc,
1972 Methods: []*descriptor.Method{
1973 {
1974 MethodDescriptorProto: meth,
1975 RequestType: msg,
1976 ResponseType: msg,
1977 Bindings: []*descriptor.Binding{
1978 {
1979 HTTPMethod: "POST",
1980 PathTmpl: httprule.Template{
1981 Version: 1,
1982 OpCodes: []int{0, 0},
1983 Template: "/v1/echo",
1984 },
1985 PathParams: []descriptor.Parameter{
1986 {
1987 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
1988 {
1989 Name: "nested",
1990 Target: nestedField,
1991 },
1992 {
1993 Name: "int32",
1994 Target: intField,
1995 },
1996 }),
1997 Target: intField,
1998 },
1999 },
2000 Body: &descriptor.Body{
2001 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
2002 {
2003 Name: "nested",
2004 Target: nestedField,
2005 },
2006 {
2007 Name: "bool",
2008 Target: boolField,
2009 },
2010 }),
2011 },
2012 },
2013 },
2014 },
2015 },
2016 },
2017 },
2018 }
2019 reg := descriptor.NewRegistry()
2020 if err := AddStreamError(reg); err != nil {
2021 t.Errorf("AddStreamError(%#v) failed with %v; want success", reg, err)
2022 return
2023 }
2024 reg.Load(&plugin.CodeGeneratorRequest{ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto}})
2025 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
2026 if err != nil {
2027 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
2028 return
2029 }
2030
2031
2032 if want, got, name := 4, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
2033 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
2034 }
2035 if _, ok := result.Paths["/v1/echo"].Post.Responses["200"]; !ok {
2036 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.Paths["/v1/echo"].Post.Responses["200"]`)
2037 } else {
2038 if want, got, name := "A successful response.(streaming responses)", result.Paths["/v1/echo"].Post.Responses["200"].Description, `result.Paths["/v1/echo"].Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) {
2039 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2040 }
2041 streamExampleExampleMessage := result.Paths["/v1/echo"].Post.Responses["200"].Schema
2042 if want, got, name := "object", streamExampleExampleMessage.Type, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) {
2043 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2044 }
2045 if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) {
2046 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2047 }
2048 streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties)
2049 if want, got, name := 2, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) {
2050 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
2051 } else {
2052 resultProperty := streamExampleExampleMessageProperties[0]
2053 if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
2054 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2055 }
2056 result := resultProperty.Value.(swaggerSchemaObject)
2057 if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(swaggerSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
2058 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2059 }
2060 errorProperty := streamExampleExampleMessageProperties[1]
2061 if want, got, name := "error", errorProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
2062 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2063 }
2064 err := errorProperty.Value.(swaggerSchemaObject)
2065 if want, got, name := "#/definitions/runtimeStreamError", err.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(swaggerSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
2066 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
2067 }
2068 }
2069 }
2070
2071
2072 if t.Failed() {
2073 t.Errorf("had: %s", file)
2074 t.Errorf("got: %s", fmt.Sprint(result))
2075 }
2076 }
2077
2078 func TestApplyTemplateRequestWithUnusedReferences(t *testing.T) {
2079 reqdesc := &protodescriptor.DescriptorProto{
2080 Name: proto.String("ExampleMessage"),
2081 Field: []*protodescriptor.FieldDescriptorProto{
2082 {
2083 Name: proto.String("string"),
2084 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
2085 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2086 Number: proto.Int32(1),
2087 },
2088 },
2089 }
2090 respdesc := &protodescriptor.DescriptorProto{
2091 Name: proto.String("EmptyMessage"),
2092 }
2093 meth := &protodescriptor.MethodDescriptorProto{
2094 Name: proto.String("Example"),
2095 InputType: proto.String("ExampleMessage"),
2096 OutputType: proto.String("EmptyMessage"),
2097 ClientStreaming: proto.Bool(false),
2098 ServerStreaming: proto.Bool(false),
2099 }
2100 svc := &protodescriptor.ServiceDescriptorProto{
2101 Name: proto.String("ExampleService"),
2102 Method: []*protodescriptor.MethodDescriptorProto{meth},
2103 }
2104
2105 req := &descriptor.Message{
2106 DescriptorProto: reqdesc,
2107 }
2108 resp := &descriptor.Message{
2109 DescriptorProto: respdesc,
2110 }
2111 stringField := &descriptor.Field{
2112 Message: req,
2113 FieldDescriptorProto: req.GetField()[0],
2114 }
2115 file := descriptor.File{
2116 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
2117 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
2118 Name: proto.String("example.proto"),
2119 Package: proto.String("example"),
2120 MessageType: []*protodescriptor.DescriptorProto{reqdesc, respdesc},
2121 Service: []*protodescriptor.ServiceDescriptorProto{svc},
2122 },
2123 GoPkg: descriptor.GoPackage{
2124 Path: "example.com/path/to/example/example.pb",
2125 Name: "example_pb",
2126 },
2127 Messages: []*descriptor.Message{req, resp},
2128 Services: []*descriptor.Service{
2129 {
2130 ServiceDescriptorProto: svc,
2131 Methods: []*descriptor.Method{
2132 {
2133 MethodDescriptorProto: meth,
2134 RequestType: req,
2135 ResponseType: resp,
2136 Bindings: []*descriptor.Binding{
2137 {
2138 HTTPMethod: "GET",
2139 PathTmpl: httprule.Template{
2140 Version: 1,
2141 OpCodes: []int{0, 0},
2142 Template: "/v1/example",
2143 },
2144 },
2145 {
2146 HTTPMethod: "POST",
2147 PathTmpl: httprule.Template{
2148 Version: 1,
2149 OpCodes: []int{0, 0},
2150 Template: "/v1/example/{string}",
2151 },
2152 PathParams: []descriptor.Parameter{
2153 {
2154 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
2155 {
2156 Name: "string",
2157 Target: stringField,
2158 },
2159 }),
2160 Target: stringField,
2161 },
2162 },
2163 Body: &descriptor.Body{
2164 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
2165 {
2166 Name: "string",
2167 Target: stringField,
2168 },
2169 }),
2170 },
2171 },
2172 },
2173 },
2174 },
2175 },
2176 },
2177 }
2178
2179 reg := descriptor.NewRegistry()
2180 reg.Load(&plugin.CodeGeneratorRequest{ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto}})
2181 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
2182 if err != nil {
2183 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
2184 return
2185 }
2186
2187
2188 if want, got, name := 1, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
2189 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
2190 }
2191
2192
2193 if t.Failed() {
2194 t.Errorf("had: %s", file)
2195 t.Errorf("got: %s", fmt.Sprint(result))
2196 }
2197 }
2198
2199 func TestApplyTemplateRequestWithBodyQueryParameters(t *testing.T) {
2200 bookDesc := &protodescriptor.DescriptorProto{
2201 Name: proto.String("Book"),
2202 Field: []*protodescriptor.FieldDescriptorProto{
2203 {
2204 Name: proto.String("name"),
2205 Label: protodescriptor.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
2206 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2207 Number: proto.Int32(1),
2208 },
2209 {
2210 Name: proto.String("id"),
2211 Label: protodescriptor.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
2212 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2213 Number: proto.Int32(2),
2214 },
2215 },
2216 }
2217 createDesc := &protodescriptor.DescriptorProto{
2218 Name: proto.String("CreateBookRequest"),
2219 Field: []*protodescriptor.FieldDescriptorProto{
2220 {
2221 Name: proto.String("parent"),
2222 Label: protodescriptor.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
2223 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2224 Number: proto.Int32(1),
2225 },
2226 {
2227 Name: proto.String("book"),
2228 Label: protodescriptor.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
2229 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2230 TypeName: proto.String("Book"),
2231 Number: proto.Int32(2),
2232 },
2233 {
2234 Name: proto.String("book_id"),
2235 Label: protodescriptor.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
2236 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2237 Number: proto.Int32(3),
2238 },
2239 },
2240 }
2241 meth := &protodescriptor.MethodDescriptorProto{
2242 Name: proto.String("CreateBook"),
2243 InputType: proto.String("CreateBookRequest"),
2244 OutputType: proto.String("Book"),
2245 }
2246 svc := &protodescriptor.ServiceDescriptorProto{
2247 Name: proto.String("BookService"),
2248 Method: []*protodescriptor.MethodDescriptorProto{meth},
2249 }
2250
2251 bookMsg := &descriptor.Message{
2252 DescriptorProto: bookDesc,
2253 }
2254 createMsg := &descriptor.Message{
2255 DescriptorProto: createDesc,
2256 }
2257
2258 parentField := &descriptor.Field{
2259 Message: createMsg,
2260 FieldDescriptorProto: createMsg.GetField()[0],
2261 }
2262 bookField := &descriptor.Field{
2263 Message: createMsg,
2264 FieldMessage: bookMsg,
2265 FieldDescriptorProto: createMsg.GetField()[1],
2266 }
2267 bookIDField := &descriptor.Field{
2268 Message: createMsg,
2269 FieldDescriptorProto: createMsg.GetField()[2],
2270 }
2271
2272 createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField}
2273
2274 file := descriptor.File{
2275 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
2276 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
2277 Name: proto.String("book.proto"),
2278 MessageType: []*protodescriptor.DescriptorProto{bookDesc, createDesc},
2279 Service: []*protodescriptor.ServiceDescriptorProto{svc},
2280 },
2281 GoPkg: descriptor.GoPackage{
2282 Path: "example.com/path/to/book.pb",
2283 Name: "book_pb",
2284 },
2285 Messages: []*descriptor.Message{bookMsg, createMsg},
2286 Services: []*descriptor.Service{
2287 {
2288 ServiceDescriptorProto: svc,
2289 Methods: []*descriptor.Method{
2290 {
2291 MethodDescriptorProto: meth,
2292 RequestType: createMsg,
2293 ResponseType: bookMsg,
2294 Bindings: []*descriptor.Binding{
2295 {
2296 HTTPMethod: "POST",
2297 PathTmpl: httprule.Template{
2298 Version: 1,
2299 OpCodes: []int{0, 0},
2300 Template: "/v1/{parent=publishers/*}/books",
2301 },
2302 PathParams: []descriptor.Parameter{
2303 {
2304 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
2305 {
2306 Name: "parent",
2307 Target: parentField,
2308 },
2309 }),
2310 Target: parentField,
2311 },
2312 },
2313 Body: &descriptor.Body{
2314 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
2315 {
2316 Name: "book",
2317 Target: bookField,
2318 },
2319 }),
2320 },
2321 },
2322 },
2323 },
2324 },
2325 },
2326 },
2327 }
2328 reg := descriptor.NewRegistry()
2329 reg.Load(&plugin.CodeGeneratorRequest{ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto}})
2330 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
2331 if err != nil {
2332 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
2333 return
2334 }
2335
2336 if _, ok := result.Paths["/v1/{parent=publishers/*}/books"].Post.Responses["200"]; !ok {
2337 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.Paths["/v1/{parent=publishers/*}/books"].Post.Responses["200"]`)
2338 } else {
2339 if want, got, name := 3, len(result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters), `len(result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters)`; !reflect.DeepEqual(got, want) {
2340 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
2341 }
2342
2343 type param struct {
2344 Name string
2345 In string
2346 Required bool
2347 }
2348
2349 p0 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[0]
2350 if want, got, name := (param{"parent", "path", true}), (param{p0.Name, p0.In, p0.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[0]`; !reflect.DeepEqual(got, want) {
2351 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
2352 }
2353 p1 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]
2354 if want, got, name := (param{"body", "body", true}), (param{p1.Name, p1.In, p1.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]`; !reflect.DeepEqual(got, want) {
2355 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
2356 }
2357 p2 := result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[2]
2358 if want, got, name := (param{"book_id", "query", false}), (param{p2.Name, p2.In, p2.Required}), `result.Paths["/v1/{parent=publishers/*}/books"].Post.Parameters[1]`; !reflect.DeepEqual(got, want) {
2359 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, got, want)
2360 }
2361 }
2362
2363
2364 if t.Failed() {
2365 t.Errorf("had: %s", file)
2366 t.Errorf("got: %s", fmt.Sprint(result))
2367 }
2368 }
2369
2370 func generateFieldsForJSONReservedName() []*descriptor.Field {
2371 fields := make([]*descriptor.Field, 0)
2372 fieldName := string("json_name")
2373 fieldJSONName := string("jsonNAME")
2374 fieldDescriptor := protodescriptor.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName}
2375 field := &descriptor.Field{FieldDescriptorProto: &fieldDescriptor}
2376 return append(fields, field)
2377 }
2378
2379 func generateMsgsForJSONReservedName() []*descriptor.Message {
2380 result := make([]*descriptor.Message, 0)
2381
2382
2383 fieldName := "field_abc"
2384 fieldJSONName := "fieldAbc"
2385 messageName1 := "message1"
2386 messageType := "pkg.a.NewType"
2387 pfd := protodescriptor.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName, TypeName: &messageType}
2388 result = append(result,
2389 &descriptor.Message{
2390 DescriptorProto: &protodescriptor.DescriptorProto{
2391 Name: &messageName1, Field: []*protodescriptor.FieldDescriptorProto{&pfd},
2392 },
2393 })
2394
2395
2396
2397
2398 messageName := "NewType"
2399 field := "field_newName"
2400 fieldJSONName2 := "RESERVEDJSONNAME"
2401 pfd2 := protodescriptor.FieldDescriptorProto{Name: &field, JsonName: &fieldJSONName2}
2402 result = append(result, &descriptor.Message{
2403 DescriptorProto: &protodescriptor.DescriptorProto{
2404 Name: &messageName, Field: []*protodescriptor.FieldDescriptorProto{&pfd2},
2405 },
2406 })
2407 return result
2408 }
2409
2410 func TestTemplateWithJsonCamelCase(t *testing.T) {
2411 var tests = []struct {
2412 input string
2413 expected string
2414 }{
2415 {"/test/{test_id}", "/test/{testId}"},
2416 {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1Id}/test2/{test2Id}"},
2417 {"/test1/{test1_id}/{test2_id}", "/test1/{test1Id}/{test2Id}"},
2418 {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1Id}/{test2Id}"},
2419 {"/test1/{test1_id1_id2}", "/test1/{test1Id1Id2}"},
2420 {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1Id1Id2}/test2/{test2Id3Id4}"},
2421 {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1Id1Id2}/{test2Id3Id4}"},
2422 {"test/{a}", "test/{a}"},
2423 {"test/{ab}", "test/{ab}"},
2424 {"test/{a_a}", "test/{aA}"},
2425 {"test/{ab_c}", "test/{abC}"},
2426 {"test/{json_name}", "test/{jsonNAME}"},
2427 {"test/{field_abc.field_newName}", "test/{fieldAbc.RESERVEDJSONNAME}"},
2428 }
2429 reg := descriptor.NewRegistry()
2430 reg.SetUseJSONNamesForFields(true)
2431 for _, data := range tests {
2432 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2433 if data.expected != actual {
2434 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2435 }
2436 }
2437 }
2438
2439 func TestTemplateWithoutJsonCamelCase(t *testing.T) {
2440 var tests = []struct {
2441 input string
2442 expected string
2443 }{
2444 {"/test/{test_id}", "/test/{test_id}"},
2445 {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1_id}/test2/{test2_id}"},
2446 {"/test1/{test1_id}/{test2_id}", "/test1/{test1_id}/{test2_id}"},
2447 {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1_id}/{test2_id}"},
2448 {"/test1/{test1_id1_id2}", "/test1/{test1_id1_id2}"},
2449 {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1_id1_id2}/test2/{test2_id3_id4}"},
2450 {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1_id1_id2}/{test2_id3_id4}"},
2451 {"test/{a}", "test/{a}"},
2452 {"test/{ab}", "test/{ab}"},
2453 {"test/{a_a}", "test/{a_a}"},
2454 {"test/{json_name}", "test/{json_name}"},
2455 {"test/{field_abc.field_newName}", "test/{field_abc.field_newName}"},
2456 }
2457 reg := descriptor.NewRegistry()
2458 reg.SetUseJSONNamesForFields(false)
2459 for _, data := range tests {
2460 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2461 if data.expected != actual {
2462 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2463 }
2464 }
2465 }
2466
2467 func TestTemplateToSwaggerPath(t *testing.T) {
2468 var tests = []struct {
2469 input string
2470 expected string
2471 }{
2472 {"/test", "/test"},
2473 {"/{test}", "/{test}"},
2474 {"/{test=prefix/*}", "/{test}"},
2475 {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
2476 {"/{test1}/{test2}", "/{test1}/{test2}"},
2477 {"/{test1}/{test2}/", "/{test1}/{test2}/"},
2478 {"/{name=prefix/*}", "/{name=prefix/*}"},
2479 {"/{name=prefix1/*/prefix2/*}", "/{name=prefix1/*/prefix2/*}"},
2480 {"/{user.name=prefix/*}", "/{user.name=prefix/*}"},
2481 {"/{user.name=prefix1/*/prefix2/*}", "/{user.name=prefix1/*/prefix2/*}"},
2482 {"/{parent=prefix/*}/children", "/{parent=prefix/*}/children"},
2483 {"/{name=prefix/*}:customMethod", "/{name=prefix/*}:customMethod"},
2484 {"/{name=prefix1/*/prefix2/*}:customMethod", "/{name=prefix1/*/prefix2/*}:customMethod"},
2485 {"/{user.name=prefix/*}:customMethod", "/{user.name=prefix/*}:customMethod"},
2486 {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name=prefix1/*/prefix2/*}:customMethod"},
2487 {"/{parent=prefix/*}/children:customMethod", "/{parent=prefix/*}/children:customMethod"},
2488 }
2489 reg := descriptor.NewRegistry()
2490 reg.SetUseJSONNamesForFields(false)
2491 for _, data := range tests {
2492 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2493 if data.expected != actual {
2494 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2495 }
2496 }
2497 reg.SetUseJSONNamesForFields(true)
2498 for _, data := range tests {
2499 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2500 if data.expected != actual {
2501 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2502 }
2503 }
2504 }
2505
2506 func BenchmarkTemplateToSwaggerPath(b *testing.B) {
2507 const input = "/{user.name=prefix1/*/prefix2/*}:customMethod"
2508
2509 b.Run("with JSON names", func(b *testing.B) {
2510 reg := descriptor.NewRegistry()
2511 reg.SetUseJSONNamesForFields(false)
2512
2513 for i := 0; i < b.N; i++ {
2514 _ = templateToSwaggerPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2515 }
2516 })
2517
2518 b.Run("without JSON names", func(b *testing.B) {
2519 reg := descriptor.NewRegistry()
2520 reg.SetUseJSONNamesForFields(true)
2521
2522 for i := 0; i < b.N; i++ {
2523 _ = templateToSwaggerPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2524 }
2525 })
2526 }
2527
2528 func TestResolveFullyQualifiedNameToSwaggerName(t *testing.T) {
2529 var tests = []struct {
2530 input string
2531 output string
2532 listOfFQMNs []string
2533 useFQNForSwaggerName bool
2534 }{
2535 {
2536 ".a.b.C",
2537 "C",
2538 []string{
2539 ".a.b.C",
2540 },
2541 false,
2542 },
2543 {
2544 ".a.b.C",
2545 "abC",
2546 []string{
2547 ".a.C",
2548 ".a.b.C",
2549 },
2550 false,
2551 },
2552 {
2553 ".a.b.C",
2554 "abC",
2555 []string{
2556 ".C",
2557 ".a.C",
2558 ".a.b.C",
2559 },
2560 false,
2561 },
2562 {
2563 ".a.b.C",
2564 "a.b.C",
2565 []string{
2566 ".C",
2567 ".a.C",
2568 ".a.b.C",
2569 },
2570 true,
2571 },
2572 }
2573
2574 for _, data := range tests {
2575 names := resolveFullyQualifiedNameToSwaggerNames(data.listOfFQMNs, data.useFQNForSwaggerName)
2576 output := names[data.input]
2577 if output != data.output {
2578 t.Errorf("Expected fullyQualifiedNameToSwaggerName(%v) to be %s but got %s",
2579 data.input, data.output, output)
2580 }
2581 }
2582 }
2583
2584 func TestFQMNtoSwaggerName(t *testing.T) {
2585 var tests = []struct {
2586 input string
2587 expected string
2588 }{
2589 {"/test", "/test"},
2590 {"/{test}", "/{test}"},
2591 {"/{test=prefix/*}", "/{test}"},
2592 {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
2593 {"/{test1}/{test2}", "/{test1}/{test2}"},
2594 {"/{test1}/{test2}/", "/{test1}/{test2}/"},
2595 }
2596 reg := descriptor.NewRegistry()
2597 reg.SetUseJSONNamesForFields(false)
2598 for _, data := range tests {
2599 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2600 if data.expected != actual {
2601 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2602 }
2603 }
2604 reg.SetUseJSONNamesForFields(true)
2605 for _, data := range tests {
2606 actual := templateToSwaggerPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
2607 if data.expected != actual {
2608 t.Errorf("Expected templateToSwaggerPath(%v) = %v, actual: %v", data.input, data.expected, actual)
2609 }
2610 }
2611 }
2612
2613 func TestSchemaOfField(t *testing.T) {
2614 type test struct {
2615 field *descriptor.Field
2616 refs refMap
2617 expected swaggerSchemaObject
2618 }
2619
2620 var fieldOptions = new(protodescriptor.FieldOptions)
2621 err := proto.SetExtension(fieldOptions, swagger_options.E_Openapiv2Field, &swagger_options.JSONSchema{
2622 Title: "field title",
2623 Description: "field description",
2624 })
2625 if err != nil {
2626 t.Errorf("proto.SetExtension() failed with %v; want success", err)
2627 }
2628
2629 tests := []test{
2630 {
2631 field: &descriptor.Field{
2632 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2633 Name: proto.String("primitive_field"),
2634 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2635 },
2636 },
2637 refs: make(refMap),
2638 expected: swaggerSchemaObject{
2639 schemaCore: schemaCore{
2640 Type: "string",
2641 },
2642 },
2643 },
2644 {
2645 field: &descriptor.Field{
2646 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2647 Name: proto.String("repeated_primitive_field"),
2648 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2649 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
2650 },
2651 },
2652 refs: make(refMap),
2653 expected: swaggerSchemaObject{
2654 schemaCore: schemaCore{
2655 Type: "array",
2656 Items: &swaggerItemsObject{
2657 Type: "string",
2658 },
2659 },
2660 },
2661 },
2662 {
2663 field: &descriptor.Field{
2664 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2665 Name: proto.String("wrapped_field"),
2666 TypeName: proto.String(".google.protobuf.StringValue"),
2667 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2668 },
2669 },
2670 refs: make(refMap),
2671 expected: swaggerSchemaObject{
2672 schemaCore: schemaCore{
2673 Type: "string",
2674 },
2675 },
2676 },
2677 {
2678 field: &descriptor.Field{
2679 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2680 Name: proto.String("repeated_wrapped_field"),
2681 TypeName: proto.String(".google.protobuf.StringValue"),
2682 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2683 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
2684 },
2685 },
2686 refs: make(refMap),
2687 expected: swaggerSchemaObject{
2688 schemaCore: schemaCore{
2689 Type: "array",
2690 Items: &swaggerItemsObject{
2691 Type: "string",
2692 },
2693 },
2694 },
2695 },
2696 {
2697 field: &descriptor.Field{
2698 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2699 Name: proto.String("wrapped_field"),
2700 TypeName: proto.String(".google.protobuf.BytesValue"),
2701 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2702 },
2703 },
2704 refs: make(refMap),
2705 expected: swaggerSchemaObject{
2706 schemaCore: schemaCore{
2707 Type: "string",
2708 Format: "byte",
2709 },
2710 },
2711 },
2712 {
2713 field: &descriptor.Field{
2714 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2715 Name: proto.String("wrapped_field"),
2716 TypeName: proto.String(".google.protobuf.Int32Value"),
2717 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2718 },
2719 },
2720 refs: make(refMap),
2721 expected: swaggerSchemaObject{
2722 schemaCore: schemaCore{
2723 Type: "integer",
2724 Format: "int32",
2725 },
2726 },
2727 },
2728 {
2729 field: &descriptor.Field{
2730 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2731 Name: proto.String("wrapped_field"),
2732 TypeName: proto.String(".google.protobuf.UInt32Value"),
2733 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2734 },
2735 },
2736 refs: make(refMap),
2737 expected: swaggerSchemaObject{
2738 schemaCore: schemaCore{
2739 Type: "integer",
2740 Format: "int64",
2741 },
2742 },
2743 },
2744 {
2745 field: &descriptor.Field{
2746 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2747 Name: proto.String("wrapped_field"),
2748 TypeName: proto.String(".google.protobuf.Int64Value"),
2749 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2750 },
2751 },
2752 refs: make(refMap),
2753 expected: swaggerSchemaObject{
2754 schemaCore: schemaCore{
2755 Type: "string",
2756 Format: "int64",
2757 },
2758 },
2759 },
2760 {
2761 field: &descriptor.Field{
2762 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2763 Name: proto.String("wrapped_field"),
2764 TypeName: proto.String(".google.protobuf.UInt64Value"),
2765 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2766 },
2767 },
2768 refs: make(refMap),
2769 expected: swaggerSchemaObject{
2770 schemaCore: schemaCore{
2771 Type: "string",
2772 Format: "uint64",
2773 },
2774 },
2775 },
2776 {
2777 field: &descriptor.Field{
2778 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2779 Name: proto.String("wrapped_field"),
2780 TypeName: proto.String(".google.protobuf.FloatValue"),
2781 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2782 },
2783 },
2784 refs: make(refMap),
2785 expected: swaggerSchemaObject{
2786 schemaCore: schemaCore{
2787 Type: "number",
2788 Format: "float",
2789 },
2790 },
2791 },
2792 {
2793 field: &descriptor.Field{
2794 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2795 Name: proto.String("wrapped_field"),
2796 TypeName: proto.String(".google.protobuf.DoubleValue"),
2797 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2798 },
2799 },
2800 refs: make(refMap),
2801 expected: swaggerSchemaObject{
2802 schemaCore: schemaCore{
2803 Type: "number",
2804 Format: "double",
2805 },
2806 },
2807 },
2808 {
2809 field: &descriptor.Field{
2810 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2811 Name: proto.String("wrapped_field"),
2812 TypeName: proto.String(".google.protobuf.BoolValue"),
2813 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2814 },
2815 },
2816 refs: make(refMap),
2817 expected: swaggerSchemaObject{
2818 schemaCore: schemaCore{
2819 Type: "boolean",
2820 },
2821 },
2822 },
2823 {
2824 field: &descriptor.Field{
2825 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2826 Name: proto.String("wrapped_field"),
2827 TypeName: proto.String(".google.protobuf.Struct"),
2828 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2829 },
2830 },
2831 refs: make(refMap),
2832 expected: swaggerSchemaObject{
2833 schemaCore: schemaCore{
2834 Type: "object",
2835 },
2836 },
2837 },
2838 {
2839 field: &descriptor.Field{
2840 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2841 Name: proto.String("wrapped_field"),
2842 TypeName: proto.String(".google.protobuf.Value"),
2843 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2844 },
2845 },
2846 refs: make(refMap),
2847 expected: swaggerSchemaObject{
2848 schemaCore: schemaCore{
2849 Type: "object",
2850 },
2851 },
2852 },
2853 {
2854 field: &descriptor.Field{
2855 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2856 Name: proto.String("wrapped_field"),
2857 TypeName: proto.String(".google.protobuf.ListValue"),
2858 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2859 },
2860 },
2861 refs: make(refMap),
2862 expected: swaggerSchemaObject{
2863 schemaCore: schemaCore{
2864 Type: "array",
2865 Items: (*swaggerItemsObject)(&schemaCore{
2866 Type: "object",
2867 }),
2868 },
2869 },
2870 },
2871 {
2872 field: &descriptor.Field{
2873 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2874 Name: proto.String("wrapped_field"),
2875 TypeName: proto.String(".google.protobuf.NullValue"),
2876 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2877 },
2878 },
2879 refs: make(refMap),
2880 expected: swaggerSchemaObject{
2881 schemaCore: schemaCore{
2882 Type: "string",
2883 },
2884 },
2885 },
2886 {
2887 field: &descriptor.Field{
2888 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2889 Name: proto.String("message_field"),
2890 TypeName: proto.String(".example.Message"),
2891 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2892 },
2893 },
2894 refs: refMap{".example.Message": struct{}{}},
2895 expected: swaggerSchemaObject{
2896 schemaCore: schemaCore{
2897 Ref: "#/definitions/exampleMessage",
2898 },
2899 },
2900 },
2901 {
2902 field: &descriptor.Field{
2903 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2904 Name: proto.String("map_field"),
2905 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
2906 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2907 TypeName: proto.String(".example.Message.MapFieldEntry"),
2908 Options: fieldOptions,
2909 },
2910 },
2911 refs: make(refMap),
2912 expected: swaggerSchemaObject{
2913 schemaCore: schemaCore{
2914 Type: "object",
2915 },
2916 AdditionalProperties: &swaggerSchemaObject{
2917 schemaCore: schemaCore{Type: "string"},
2918 },
2919 Title: "field title",
2920 Description: "field description",
2921 },
2922 },
2923 {
2924 field: &descriptor.Field{
2925 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2926 Name: proto.String("array_field"),
2927 Label: protodescriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
2928 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2929 Options: fieldOptions,
2930 },
2931 },
2932 refs: make(refMap),
2933 expected: swaggerSchemaObject{
2934 schemaCore: schemaCore{
2935 Type: "array",
2936 Items: (*swaggerItemsObject)(&schemaCore{Type: "string"}),
2937 },
2938 Title: "field title",
2939 Description: "field description",
2940 },
2941 },
2942 {
2943 field: &descriptor.Field{
2944 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2945 Name: proto.String("primitive_field"),
2946 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
2947 Type: protodescriptor.FieldDescriptorProto_TYPE_INT32.Enum(),
2948 Options: fieldOptions,
2949 },
2950 },
2951 refs: make(refMap),
2952 expected: swaggerSchemaObject{
2953 schemaCore: schemaCore{
2954 Type: "integer",
2955 Format: "int32",
2956 },
2957 Title: "field title",
2958 Description: "field description",
2959 },
2960 },
2961 {
2962 field: &descriptor.Field{
2963 FieldDescriptorProto: &protodescriptor.FieldDescriptorProto{
2964 Name: proto.String("message_field"),
2965 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
2966 Type: protodescriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
2967 TypeName: proto.String(".example.Empty"),
2968 Options: fieldOptions,
2969 },
2970 },
2971 refs: refMap{".example.Empty": struct{}{}},
2972 expected: swaggerSchemaObject{
2973 schemaCore: schemaCore{
2974 Ref: "#/definitions/exampleEmpty",
2975 },
2976 Title: "field title",
2977 Description: "field description",
2978 },
2979 },
2980 }
2981
2982 reg := descriptor.NewRegistry()
2983 reg.Load(&plugin.CodeGeneratorRequest{
2984 ProtoFile: []*protodescriptor.FileDescriptorProto{
2985 {
2986 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
2987 Name: proto.String("example.proto"),
2988 Package: proto.String("example"),
2989 Dependency: []string{},
2990 MessageType: []*protodescriptor.DescriptorProto{
2991 {
2992 Name: proto.String("Message"),
2993 Field: []*protodescriptor.FieldDescriptorProto{
2994 {
2995 Name: proto.String("value"),
2996 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
2997 },
2998 },
2999 NestedType: []*protodescriptor.DescriptorProto{
3000 {
3001 Name: proto.String("MapFieldEntry"),
3002 Options: &protodescriptor.MessageOptions{MapEntry: proto.Bool(true)},
3003 Field: []*protodescriptor.FieldDescriptorProto{
3004 {},
3005 {
3006 Name: proto.String("value"),
3007 Label: protodescriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
3008 Type: protodescriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
3009 },
3010 },
3011 },
3012 },
3013 },
3014 {
3015 Name: proto.String("Empty"),
3016 },
3017 },
3018 EnumType: []*protodescriptor.EnumDescriptorProto{
3019 {
3020 Name: proto.String("Message"),
3021 },
3022 },
3023 Service: []*protodescriptor.ServiceDescriptorProto{},
3024 },
3025 },
3026 })
3027
3028 for _, test := range tests {
3029 refs := make(refMap)
3030 actual := schemaOfField(test.field, reg, refs)
3031 expectedSchemaObject := test.expected
3032 if e, a := expectedSchemaObject, actual; !reflect.DeepEqual(a, e) {
3033 t.Errorf("Expected schemaOfField(%v) = %v, actual: %v", test.field, e, a)
3034 }
3035 if !reflect.DeepEqual(refs, test.refs) {
3036 t.Errorf("Expected schemaOfField(%v) to add refs %v, not %v", test.field, test.refs, refs)
3037 }
3038 }
3039 }
3040
3041 func TestRenderMessagesAsDefinition(t *testing.T) {
3042
3043 tests := []struct {
3044 descr string
3045 msgDescs []*protodescriptor.DescriptorProto
3046 schema map[string]swagger_options.Schema
3047 defs swaggerDefinitionsObject
3048 }{
3049 {
3050 descr: "no swagger options",
3051 msgDescs: []*protodescriptor.DescriptorProto{
3052 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3053 },
3054 schema: map[string]swagger_options.Schema{},
3055 defs: map[string]swaggerSchemaObject{
3056 "Message": swaggerSchemaObject{schemaCore: schemaCore{Type: "object"}},
3057 },
3058 },
3059 {
3060 descr: "example option",
3061 msgDescs: []*protodescriptor.DescriptorProto{
3062 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3063 },
3064 schema: map[string]swagger_options.Schema{
3065 "Message": swagger_options.Schema{
3066 ExampleString: `{"foo":"bar"}`,
3067 },
3068 },
3069 defs: map[string]swaggerSchemaObject{
3070 "Message": swaggerSchemaObject{schemaCore: schemaCore{
3071 Type: "object",
3072 Example: json.RawMessage(`{"foo":"bar"}`),
3073 }},
3074 },
3075 },
3076 {
3077 descr: "example option with something non-json",
3078 msgDescs: []*protodescriptor.DescriptorProto{
3079 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3080 },
3081 schema: map[string]swagger_options.Schema{
3082 "Message": swagger_options.Schema{
3083 ExampleString: `XXXX anything goes XXXX`,
3084 },
3085 },
3086 defs: map[string]swaggerSchemaObject{
3087 "Message": swaggerSchemaObject{schemaCore: schemaCore{
3088 Type: "object",
3089 Example: json.RawMessage(`XXXX anything goes XXXX`),
3090 }},
3091 },
3092 },
3093 {
3094 descr: "external docs option",
3095 msgDescs: []*protodescriptor.DescriptorProto{
3096 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3097 },
3098 schema: map[string]swagger_options.Schema{
3099 "Message": swagger_options.Schema{
3100 ExternalDocs: &swagger_options.ExternalDocumentation{
3101 Description: "glorious docs",
3102 Url: "https://nada",
3103 },
3104 },
3105 },
3106 defs: map[string]swaggerSchemaObject{
3107 "Message": swaggerSchemaObject{
3108 schemaCore: schemaCore{
3109 Type: "object",
3110 },
3111 ExternalDocs: &swaggerExternalDocumentationObject{
3112 Description: "glorious docs",
3113 URL: "https://nada",
3114 },
3115 },
3116 },
3117 },
3118 {
3119 descr: "JSONSchema options",
3120 msgDescs: []*protodescriptor.DescriptorProto{
3121 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3122 },
3123 schema: map[string]swagger_options.Schema{
3124 "Message": swagger_options.Schema{
3125 JsonSchema: &swagger_options.JSONSchema{
3126 Title: "title",
3127 Description: "desc",
3128 MultipleOf: 100,
3129 Maximum: 101,
3130 ExclusiveMaximum: true,
3131 Minimum: 1,
3132 ExclusiveMinimum: true,
3133 MaxLength: 10,
3134 MinLength: 3,
3135 Pattern: "[a-z]+",
3136 MaxItems: 20,
3137 MinItems: 2,
3138 UniqueItems: true,
3139 MaxProperties: 33,
3140 MinProperties: 22,
3141 Required: []string{"req"},
3142 ReadOnly: true,
3143 },
3144 },
3145 },
3146 defs: map[string]swaggerSchemaObject{
3147 "Message": swaggerSchemaObject{
3148 schemaCore: schemaCore{
3149 Type: "object",
3150 },
3151 Title: "title",
3152 Description: "desc",
3153 MultipleOf: 100,
3154 Maximum: 101,
3155 ExclusiveMaximum: true,
3156 Minimum: 1,
3157 ExclusiveMinimum: true,
3158 MaxLength: 10,
3159 MinLength: 3,
3160 Pattern: "[a-z]+",
3161 MaxItems: 20,
3162 MinItems: 2,
3163 UniqueItems: true,
3164 MaxProperties: 33,
3165 MinProperties: 22,
3166 Required: []string{"req"},
3167 ReadOnly: true,
3168 },
3169 },
3170 },
3171 }
3172
3173 for _, test := range tests {
3174 t.Run(test.descr, func(t *testing.T) {
3175
3176 msgs := []*descriptor.Message{}
3177 for _, msgdesc := range test.msgDescs {
3178 msgdesc.Options = &protodescriptor.MessageOptions{}
3179 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
3180 }
3181
3182 reg := descriptor.NewRegistry()
3183 file := descriptor.File{
3184 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
3185 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
3186 Name: proto.String("example.proto"),
3187 Package: proto.String("example"),
3188 Dependency: []string{},
3189 MessageType: test.msgDescs,
3190 EnumType: []*protodescriptor.EnumDescriptorProto{},
3191 Service: []*protodescriptor.ServiceDescriptorProto{},
3192 },
3193 Messages: msgs,
3194 }
3195 reg.Load(&plugin.CodeGeneratorRequest{
3196 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
3197 })
3198
3199 msgMap := map[string]*descriptor.Message{}
3200 for _, d := range test.msgDescs {
3201 name := d.GetName()
3202 msg, err := reg.LookupMsg("example", name)
3203 if err != nil {
3204 t.Fatalf("lookup message %v: %v", name, err)
3205 }
3206 msgMap[msg.FQMN()] = msg
3207
3208 if schema, ok := test.schema[name]; ok {
3209 err := proto.SetExtension(d.Options, swagger_options.E_Openapiv2Schema, &schema)
3210 if err != nil {
3211 t.Fatalf("SetExtension(%s, ...) returned error: %v", msg, err)
3212 }
3213 }
3214 }
3215
3216 refs := make(refMap)
3217 actual := make(swaggerDefinitionsObject)
3218 renderMessagesAsDefinition(msgMap, actual, reg, refs)
3219
3220 if !reflect.DeepEqual(actual, test.defs) {
3221 t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
3222 }
3223 })
3224 }
3225 }
3226
3227 func TestUpdateSwaggerDataFromComments(t *testing.T) {
3228
3229 tests := []struct {
3230 descr string
3231 swaggerObject interface{}
3232 comments string
3233 expectedError error
3234 expectedSwaggerObject interface{}
3235 useGoTemplate bool
3236 }{
3237 {
3238 descr: "empty comments",
3239 swaggerObject: nil,
3240 expectedSwaggerObject: nil,
3241 comments: "",
3242 expectedError: nil,
3243 },
3244 {
3245 descr: "set field to read only",
3246 swaggerObject: &swaggerSchemaObject{},
3247 expectedSwaggerObject: &swaggerSchemaObject{
3248 ReadOnly: true,
3249 Description: "... Output only. ...",
3250 },
3251 comments: "... Output only. ...",
3252 expectedError: nil,
3253 },
3254 {
3255 descr: "set title",
3256 swaggerObject: &swaggerSchemaObject{},
3257 expectedSwaggerObject: &swaggerSchemaObject{
3258 Title: "Comment with no trailing dot",
3259 },
3260 comments: "Comment with no trailing dot",
3261 expectedError: nil,
3262 },
3263 {
3264 descr: "set description",
3265 swaggerObject: &swaggerSchemaObject{},
3266 expectedSwaggerObject: &swaggerSchemaObject{
3267 Description: "Comment with trailing dot.",
3268 },
3269 comments: "Comment with trailing dot.",
3270 expectedError: nil,
3271 },
3272 {
3273 descr: "use info object",
3274 swaggerObject: &swaggerObject{
3275 Info: swaggerInfoObject{},
3276 },
3277 expectedSwaggerObject: &swaggerObject{
3278 Info: swaggerInfoObject{
3279 Description: "Comment with trailing dot.",
3280 },
3281 },
3282 comments: "Comment with trailing dot.",
3283 expectedError: nil,
3284 },
3285 {
3286 descr: "multi line comment with title",
3287 swaggerObject: &swaggerSchemaObject{},
3288 expectedSwaggerObject: &swaggerSchemaObject{
3289 Title: "First line",
3290 Description: "Second line",
3291 },
3292 comments: "First line\n\nSecond line",
3293 expectedError: nil,
3294 },
3295 {
3296 descr: "multi line comment no title",
3297 swaggerObject: &swaggerSchemaObject{},
3298 expectedSwaggerObject: &swaggerSchemaObject{
3299 Description: "First line.\n\nSecond line",
3300 },
3301 comments: "First line.\n\nSecond line",
3302 expectedError: nil,
3303 },
3304 {
3305 descr: "multi line comment with summary with dot",
3306 swaggerObject: &swaggerOperationObject{},
3307 expectedSwaggerObject: &swaggerOperationObject{
3308 Summary: "First line.",
3309 Description: "Second line",
3310 },
3311 comments: "First line.\n\nSecond line",
3312 expectedError: nil,
3313 },
3314 {
3315 descr: "multi line comment with summary no dot",
3316 swaggerObject: &swaggerOperationObject{},
3317 expectedSwaggerObject: &swaggerOperationObject{
3318 Summary: "First line",
3319 Description: "Second line",
3320 },
3321 comments: "First line\n\nSecond line",
3322 expectedError: nil,
3323 },
3324 {
3325 descr: "multi line comment with summary no dot",
3326 swaggerObject: &schemaCore{},
3327 expectedSwaggerObject: &schemaCore{},
3328 comments: "Any comment",
3329 expectedError: errors.New("no description nor summary property"),
3330 },
3331 {
3332 descr: "without use_go_template",
3333 swaggerObject: &swaggerSchemaObject{},
3334 expectedSwaggerObject: &swaggerSchemaObject{
3335 Title: "First line",
3336 Description: "{{import \"documentation.md\"}}",
3337 },
3338 comments: "First line\n\n{{import \"documentation.md\"}}",
3339 expectedError: nil,
3340 },
3341 {
3342 descr: "error with use_go_template",
3343 swaggerObject: &swaggerSchemaObject{},
3344 expectedSwaggerObject: &swaggerSchemaObject{
3345 Title: "First line",
3346 Description: "open noneexistingfile.txt: no such file or directory",
3347 },
3348 comments: "First line\n\n{{import \"noneexistingfile.txt\"}}",
3349 expectedError: nil,
3350 useGoTemplate: true,
3351 },
3352 {
3353 descr: "template with use_go_template",
3354 swaggerObject: &swaggerSchemaObject{},
3355 expectedSwaggerObject: &swaggerSchemaObject{
3356 Title: "Template",
3357 Description: `Description "which means nothing"`,
3358 },
3359 comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3360 expectedError: nil,
3361 useGoTemplate: true,
3362 },
3363 }
3364
3365 for _, test := range tests {
3366 t.Run(test.descr, func(t *testing.T) {
3367 reg := descriptor.NewRegistry()
3368 if test.useGoTemplate {
3369 reg.SetUseGoTemplate(true)
3370 }
3371 err := updateSwaggerDataFromComments(reg, test.swaggerObject, nil, test.comments, false)
3372 if test.expectedError == nil {
3373 if err != nil {
3374 t.Errorf("unexpected error '%v'", err)
3375 }
3376 if !reflect.DeepEqual(test.swaggerObject, test.expectedSwaggerObject) {
3377 t.Errorf("swaggerObject was not updated corretly, expected '%+v', got '%+v'", test.expectedSwaggerObject, test.swaggerObject)
3378 }
3379 } else {
3380 if err == nil {
3381 t.Error("expected update error not returned")
3382 }
3383 if !reflect.DeepEqual(test.swaggerObject, test.expectedSwaggerObject) {
3384 t.Errorf("swaggerObject was not updated corretly, expected '%+v', got '%+v'", test.expectedSwaggerObject, test.swaggerObject)
3385 }
3386 if err.Error() != test.expectedError.Error() {
3387 t.Errorf("expected error malformed, expected %q, got %q", test.expectedError.Error(), err.Error())
3388 }
3389 }
3390 })
3391 }
3392 }
3393
3394 func TestMessageOptionsWithGoTemplate(t *testing.T) {
3395 tests := []struct {
3396 descr string
3397 msgDescs []*protodescriptor.DescriptorProto
3398 schema map[string]swagger_options.Schema
3399 defs swaggerDefinitionsObject
3400 useGoTemplate bool
3401 }{
3402 {
3403 descr: "external docs option",
3404 msgDescs: []*protodescriptor.DescriptorProto{
3405 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3406 },
3407 schema: map[string]swagger_options.Schema{
3408 "Message": swagger_options.Schema{
3409 JsonSchema: &swagger_options.JSONSchema{
3410 Title: "{{.Name}}",
3411 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3412 },
3413 ExternalDocs: &swagger_options.ExternalDocumentation{
3414 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3415 },
3416 },
3417 },
3418 defs: map[string]swaggerSchemaObject{
3419 "Message": swaggerSchemaObject{
3420 schemaCore: schemaCore{
3421 Type: "object",
3422 },
3423 Title: "Message",
3424 Description: `Description "which means nothing"`,
3425 ExternalDocs: &swaggerExternalDocumentationObject{
3426 Description: `Description "which means nothing"`,
3427 },
3428 },
3429 },
3430 useGoTemplate: true,
3431 },
3432 {
3433 descr: "external docs option",
3434 msgDescs: []*protodescriptor.DescriptorProto{
3435 &protodescriptor.DescriptorProto{Name: proto.String("Message")},
3436 },
3437 schema: map[string]swagger_options.Schema{
3438 "Message": swagger_options.Schema{
3439 JsonSchema: &swagger_options.JSONSchema{
3440 Title: "{{.Name}}",
3441 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3442 },
3443 ExternalDocs: &swagger_options.ExternalDocumentation{
3444 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3445 },
3446 },
3447 },
3448 defs: map[string]swaggerSchemaObject{
3449 "Message": swaggerSchemaObject{
3450 schemaCore: schemaCore{
3451 Type: "object",
3452 },
3453 Title: "{{.Name}}",
3454 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3455 ExternalDocs: &swaggerExternalDocumentationObject{
3456 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
3457 },
3458 },
3459 },
3460 useGoTemplate: false,
3461 },
3462 }
3463
3464 for _, test := range tests {
3465 t.Run(test.descr, func(t *testing.T) {
3466
3467 msgs := []*descriptor.Message{}
3468 for _, msgdesc := range test.msgDescs {
3469 msgdesc.Options = &protodescriptor.MessageOptions{}
3470 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
3471 }
3472
3473 reg := descriptor.NewRegistry()
3474 reg.SetUseGoTemplate(test.useGoTemplate)
3475 file := descriptor.File{
3476 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
3477 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
3478 Name: proto.String("example.proto"),
3479 Package: proto.String("example"),
3480 Dependency: []string{},
3481 MessageType: test.msgDescs,
3482 EnumType: []*protodescriptor.EnumDescriptorProto{},
3483 Service: []*protodescriptor.ServiceDescriptorProto{},
3484 },
3485 Messages: msgs,
3486 }
3487 reg.Load(&plugin.CodeGeneratorRequest{
3488 ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto},
3489 })
3490
3491 msgMap := map[string]*descriptor.Message{}
3492 for _, d := range test.msgDescs {
3493 name := d.GetName()
3494 msg, err := reg.LookupMsg("example", name)
3495 if err != nil {
3496 t.Fatalf("lookup message %v: %v", name, err)
3497 }
3498 msgMap[msg.FQMN()] = msg
3499
3500 if schema, ok := test.schema[name]; ok {
3501 err := proto.SetExtension(d.Options, swagger_options.E_Openapiv2Schema, &schema)
3502 if err != nil {
3503 t.Fatalf("SetExtension(%s, ...) returned error: %v", msg, err)
3504 }
3505 }
3506 }
3507
3508 refs := make(refMap)
3509 actual := make(swaggerDefinitionsObject)
3510 renderMessagesAsDefinition(msgMap, actual, reg, refs)
3511
3512 if !reflect.DeepEqual(actual, test.defs) {
3513 t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
3514 }
3515 })
3516 }
3517 }
3518
3519 func TestTemplateWithoutErrorDefinition(t *testing.T) {
3520 msgdesc := &protodescriptor.DescriptorProto{
3521 Name: proto.String("ExampleMessage"),
3522 Field: []*protodescriptor.FieldDescriptorProto{},
3523 }
3524 meth := &protodescriptor.MethodDescriptorProto{
3525 Name: proto.String("Echo"),
3526 InputType: proto.String("ExampleMessage"),
3527 OutputType: proto.String("ExampleMessage"),
3528 }
3529 svc := &protodescriptor.ServiceDescriptorProto{
3530 Name: proto.String("ExampleService"),
3531 Method: []*protodescriptor.MethodDescriptorProto{meth},
3532 }
3533
3534 msg := &descriptor.Message{
3535 DescriptorProto: msgdesc,
3536 }
3537
3538 file := descriptor.File{
3539 FileDescriptorProto: &protodescriptor.FileDescriptorProto{
3540 SourceCodeInfo: &protodescriptor.SourceCodeInfo{},
3541 Name: proto.String("example.proto"),
3542 Package: proto.String("example"),
3543 MessageType: []*protodescriptor.DescriptorProto{msgdesc, msgdesc},
3544 Service: []*protodescriptor.ServiceDescriptorProto{svc},
3545 },
3546 GoPkg: descriptor.GoPackage{
3547 Path: "example.com/path/to/example/example.pb",
3548 Name: "example_pb",
3549 },
3550 Messages: []*descriptor.Message{msg},
3551 Services: []*descriptor.Service{
3552 {
3553 ServiceDescriptorProto: svc,
3554 Methods: []*descriptor.Method{
3555 {
3556 MethodDescriptorProto: meth,
3557 RequestType: msg,
3558 ResponseType: msg,
3559 Bindings: []*descriptor.Binding{
3560 {
3561 HTTPMethod: "POST",
3562 PathTmpl: httprule.Template{
3563 Version: 1,
3564 OpCodes: []int{0, 0},
3565 Template: "/v1/echo",
3566 },
3567 Body: &descriptor.Body{
3568 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
3569 },
3570 },
3571 },
3572 },
3573 },
3574 },
3575 },
3576 }
3577 reg := descriptor.NewRegistry()
3578 reg.Load(&plugin.CodeGeneratorRequest{ProtoFile: []*protodescriptor.FileDescriptorProto{file.FileDescriptorProto}})
3579 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
3580 if err != nil {
3581 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
3582 return
3583 }
3584
3585 defRsp, ok := result.Paths["/v1/echo"].Post.Responses["default"]
3586 if !ok {
3587 return
3588 }
3589
3590 ref := defRsp.Schema.schemaCore.Ref
3591 refName := strings.TrimPrefix(ref, "#/definitions/")
3592 if refName == "" {
3593 t.Fatal("created default Error response with empty reflink")
3594 }
3595
3596 if _, ok := result.Definitions[refName]; !ok {
3597 t.Errorf("default Error response with reflink '%v', but its definition was not found", refName)
3598 }
3599 }
3600
3601 func Test_getReservedJsonName(t *testing.T) {
3602 type args struct {
3603 fieldName string
3604 messageNameToFieldsToJSONName map[string]map[string]string
3605 fieldNameToType map[string]string
3606 }
3607 tests := []struct {
3608 name string
3609 args args
3610 want string
3611 }{
3612 {
3613 "test case 1: single dot use case",
3614 args{
3615 fieldName: "abc.a_1",
3616 messageNameToFieldsToJSONName: map[string]map[string]string{
3617 "Msg": {
3618 "a_1": "a1JSONNAME",
3619 "b_1": "b1JSONNAME",
3620 },
3621 },
3622 fieldNameToType: map[string]string{
3623 "abc": "pkg1.test.Msg",
3624 "bcd": "pkg1.test.Msg",
3625 },
3626 },
3627 "a1JSONNAME",
3628 },
3629 {
3630 "test case 2: single dot use case with no existing field",
3631 args{
3632 fieldName: "abc.d_1",
3633 messageNameToFieldsToJSONName: map[string]map[string]string{
3634 "Msg": {
3635 "a_1": "a1JSONNAME",
3636 "b_1": "b1JSONNAME",
3637 },
3638 },
3639 fieldNameToType: map[string]string{
3640 "abc": "pkg1.test.Msg",
3641 "bcd": "pkg1.test.Msg",
3642 },
3643 },
3644 "",
3645 },
3646 {
3647 "test case 3: double dot use case",
3648 args{
3649 fieldName: "pkg.abc.a_1",
3650 messageNameToFieldsToJSONName: map[string]map[string]string{
3651 "Msg": {
3652 "a_1": "a1JSONNAME",
3653 "b_1": "b1JSONNAME",
3654 },
3655 },
3656 fieldNameToType: map[string]string{
3657 "abc": "pkg1.test.Msg",
3658 "bcd": "pkg1.test.Msg",
3659 },
3660 },
3661 "a1JSONNAME",
3662 },
3663 {
3664 "test case 4: double dot use case with a not existed field",
3665 args{
3666 fieldName: "pkg.abc.c_1",
3667 messageNameToFieldsToJSONName: map[string]map[string]string{
3668 "Msg": {
3669 "a_1": "a1JSONNAME",
3670 "b_1": "b1JSONNAME",
3671 },
3672 },
3673 fieldNameToType: map[string]string{
3674 "abc": "pkg1.test.Msg",
3675 "bcd": "pkg1.test.Msg",
3676 },
3677 },
3678 "",
3679 },
3680 }
3681 for _, tt := range tests {
3682 t.Run(tt.name, func(t *testing.T) {
3683 if got := getReservedJSONName(tt.args.fieldName, tt.args.messageNameToFieldsToJSONName, tt.args.fieldNameToType); got != tt.want {
3684 t.Errorf("getReservedJSONName() = %v, want %v", got, tt.want)
3685 }
3686 })
3687 }
3688 }
3689
View as plain text