1 package descriptor
2
3 import (
4 "reflect"
5 "strings"
6 "testing"
7
8 "github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule"
9 "google.golang.org/protobuf/compiler/protogen"
10 "google.golang.org/protobuf/encoding/prototext"
11 "google.golang.org/protobuf/proto"
12 "google.golang.org/protobuf/types/descriptorpb"
13 )
14
15 func compilePath(t *testing.T, path string) httprule.Template {
16 parsed, err := httprule.Parse(path)
17 if err != nil {
18 t.Fatalf("httprule.Parse(%q) failed with %v; want success", path, err)
19 }
20 return parsed.Compile()
21 }
22
23 func testExtractServices(t *testing.T, input []*descriptorpb.FileDescriptorProto, target string, wantSvcs []*Service) {
24 testExtractServicesWithRegistry(t, NewRegistry(), input, target, wantSvcs)
25 }
26
27 func testExtractServicesWithRegistry(t *testing.T, reg *Registry, input []*descriptorpb.FileDescriptorProto, target string, wantSvcs []*Service) {
28 for _, file := range input {
29 reg.loadFile(file.GetName(), &protogen.File{
30 Proto: file,
31 })
32 }
33 err := reg.loadServices(reg.files[target])
34 if err != nil {
35 t.Errorf("loadServices(%q) failed with %v; want success; files=%v", target, err, input)
36 }
37
38 file := reg.files[target]
39 svcs := file.Services
40 var i int
41 for i = 0; i < len(svcs) && i < len(wantSvcs); i++ {
42 svc, wantSvc := svcs[i], wantSvcs[i]
43 if got, want := svc.ServiceDescriptorProto, wantSvc.ServiceDescriptorProto; !proto.Equal(got, want) {
44 t.Errorf("svcs[%d].ServiceDescriptorProto = %v; want %v; input = %v", i, got, want, input)
45 continue
46 }
47 var j int
48 for j = 0; j < len(svc.Methods) && j < len(wantSvc.Methods); j++ {
49 meth, wantMeth := svc.Methods[j], wantSvc.Methods[j]
50 if got, want := meth.MethodDescriptorProto, wantMeth.MethodDescriptorProto; !proto.Equal(got, want) {
51 t.Errorf("svcs[%d].Methods[%d].MethodDescriptorProto = %v; want %v; input = %v", i, j, got, want, input)
52 continue
53 }
54 if got, want := meth.RequestType, wantMeth.RequestType; got.FQMN() != want.FQMN() {
55 t.Errorf("svcs[%d].Methods[%d].RequestType = %s; want %s; input = %v", i, j, got.FQMN(), want.FQMN(), input)
56 }
57 if got, want := meth.ResponseType, wantMeth.ResponseType; got.FQMN() != want.FQMN() {
58 t.Errorf("svcs[%d].Methods[%d].ResponseType = %s; want %s; input = %v", i, j, got.FQMN(), want.FQMN(), input)
59 }
60 var k int
61 for k = 0; k < len(meth.Bindings) && k < len(wantMeth.Bindings); k++ {
62 binding, wantBinding := meth.Bindings[k], wantMeth.Bindings[k]
63 if got, want := binding.Index, wantBinding.Index; got != want {
64 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Index = %d; want %d; input = %v", i, j, k, got, want, input)
65 }
66 if got, want := binding.PathTmpl, wantBinding.PathTmpl; !reflect.DeepEqual(got, want) {
67 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathTmpl = %#v; want %#v; input = %v", i, j, k, got, want, input)
68 }
69 if got, want := binding.HTTPMethod, wantBinding.HTTPMethod; got != want {
70 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].HTTPMethod = %q; want %q; input = %v", i, j, k, got, want, input)
71 }
72
73 var l int
74 for l = 0; l < len(binding.PathParams) && l < len(wantBinding.PathParams); l++ {
75 param, wantParam := binding.PathParams[l], wantBinding.PathParams[l]
76 if got, want := param.FieldPath.String(), wantParam.FieldPath.String(); got != want {
77 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d].FieldPath.String() = %q; want %q; input = %v", i, j, k, l, got, want, input)
78 continue
79 }
80 for m := 0; m < len(param.FieldPath) && m < len(wantParam.FieldPath); m++ {
81 field, wantField := param.FieldPath[m].Target, wantParam.FieldPath[m].Target
82 if got, want := field.FieldDescriptorProto, wantField.FieldDescriptorProto; !proto.Equal(got, want) {
83 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d].FieldPath[%d].Target.FieldDescriptorProto = %v; want %v; input = %v", i, j, k, l, m, got, want, input)
84 }
85 }
86 }
87 for ; l < len(binding.PathParams); l++ {
88 got := binding.PathParams[l].FieldPath.String()
89 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d] = %q; want it to be missing; input = %v", i, j, k, l, got, input)
90 }
91 for ; l < len(wantBinding.PathParams); l++ {
92 want := wantBinding.PathParams[l].FieldPath.String()
93 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].PathParams[%d] missing; want %q; input = %v", i, j, k, l, want, input)
94 }
95
96 if got, want := (binding.Body != nil), (wantBinding.Body != nil); got != want {
97 if got {
98 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body = %q; want it to be missing; input = %v", i, j, k, binding.Body.FieldPath.String(), input)
99 } else {
100 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body missing; want %q; input = %v", i, j, k, wantBinding.Body.FieldPath.String(), input)
101 }
102 } else if binding.Body != nil {
103 if got, want := binding.Body.FieldPath.String(), wantBinding.Body.FieldPath.String(); got != want {
104 t.Errorf("svcs[%d].Methods[%d].Bindings[%d].Body = %q; want %q; input = %v", i, j, k, got, want, input)
105 }
106 }
107 }
108 for ; k < len(meth.Bindings); k++ {
109 got := meth.Bindings[k]
110 t.Errorf("svcs[%d].Methods[%d].Bindings[%d] = %v; want it to be missing; input = %v", i, j, k, got, input)
111 }
112 for ; k < len(wantMeth.Bindings); k++ {
113 want := wantMeth.Bindings[k]
114 t.Errorf("svcs[%d].Methods[%d].Bindings[%d] missing; want %v; input = %v", i, j, k, want, input)
115 }
116 }
117 for ; j < len(svc.Methods); j++ {
118 got := svc.Methods[j].MethodDescriptorProto
119 t.Errorf("svcs[%d].Methods[%d] = %v; want it to be missing; input = %v", i, j, got, input)
120 }
121 for ; j < len(wantSvc.Methods); j++ {
122 want := wantSvc.Methods[j].MethodDescriptorProto
123 t.Errorf("svcs[%d].Methods[%d] missing; want %v; input = %v", i, j, want, input)
124 }
125 }
126 for ; i < len(svcs); i++ {
127 got := svcs[i].ServiceDescriptorProto
128 t.Errorf("svcs[%d] = %v; want it to be missing; input = %v", i, got, input)
129 }
130 for ; i < len(wantSvcs); i++ {
131 want := wantSvcs[i].ServiceDescriptorProto
132 t.Errorf("svcs[%d] missing; want %v; input = %v", i, want, input)
133 }
134 }
135
136 func crossLinkFixture(f *File) *File {
137 for _, m := range f.Messages {
138 m.File = f
139 for _, f := range m.Fields {
140 f.Message = m
141 }
142 }
143 for _, svc := range f.Services {
144 svc.File = f
145 for _, m := range svc.Methods {
146 m.Service = svc
147 for _, b := range m.Bindings {
148 b.Method = m
149 for _, param := range b.PathParams {
150 param.Method = m
151 }
152 }
153 }
154 }
155 for _, e := range f.Enums {
156 e.File = f
157 }
158 return f
159 }
160
161 func TestExtractServicesSimple(t *testing.T) {
162 src := `
163 name: "path/to/example.proto",
164 package: "example"
165 message_type <
166 name: "StringMessage"
167 field <
168 name: "string"
169 number: 1
170 label: LABEL_OPTIONAL
171 type: TYPE_STRING
172 >
173 >
174 service <
175 name: "ExampleService"
176 method <
177 name: "Echo"
178 input_type: "StringMessage"
179 output_type: "StringMessage"
180 options <
181 [google.api.http] <
182 post: "/v1/example/echo"
183 body: "*"
184 >
185 >
186 >
187 >
188 `
189 var fd descriptorpb.FileDescriptorProto
190 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
191 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
192 }
193 msg := &Message{
194 DescriptorProto: fd.MessageType[0],
195 Fields: []*Field{
196 {
197 FieldDescriptorProto: fd.MessageType[0].Field[0],
198 },
199 },
200 }
201 file := &File{
202 FileDescriptorProto: &fd,
203 GoPkg: GoPackage{
204 Path: "path/to/example.pb",
205 Name: "example_pb",
206 },
207 Messages: []*Message{msg},
208 Services: []*Service{
209 {
210 ServiceDescriptorProto: fd.Service[0],
211 Methods: []*Method{
212 {
213 MethodDescriptorProto: fd.Service[0].Method[0],
214 RequestType: msg,
215 ResponseType: msg,
216 Bindings: []*Binding{
217 {
218 PathTmpl: compilePath(t, "/v1/example/echo"),
219 HTTPMethod: "POST",
220 Body: &Body{FieldPath: nil},
221 },
222 },
223 },
224 },
225 },
226 },
227 }
228
229 crossLinkFixture(file)
230 testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
231 }
232
233 func TestExtractServicesWithoutAnnotation(t *testing.T) {
234 src := `
235 name: "path/to/example.proto",
236 package: "example"
237 message_type <
238 name: "StringMessage"
239 field <
240 name: "string"
241 number: 1
242 label: LABEL_OPTIONAL
243 type: TYPE_STRING
244 >
245 >
246 service <
247 name: "ExampleService"
248 method <
249 name: "Echo"
250 input_type: "StringMessage"
251 output_type: "StringMessage"
252 >
253 >
254 `
255 var fd descriptorpb.FileDescriptorProto
256 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
257 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
258 }
259 msg := &Message{
260 DescriptorProto: fd.MessageType[0],
261 Fields: []*Field{
262 {
263 FieldDescriptorProto: fd.MessageType[0].Field[0],
264 },
265 },
266 }
267 file := &File{
268 FileDescriptorProto: &fd,
269 GoPkg: GoPackage{
270 Path: "path/to/example.pb",
271 Name: "example_pb",
272 },
273 Messages: []*Message{msg},
274 Services: []*Service{
275 {
276 ServiceDescriptorProto: fd.Service[0],
277 Methods: []*Method{
278 {
279 MethodDescriptorProto: fd.Service[0].Method[0],
280 RequestType: msg,
281 ResponseType: msg,
282 },
283 },
284 },
285 },
286 }
287
288 crossLinkFixture(file)
289 testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
290 }
291
292 func TestExtractServicesGenerateUnboundMethods(t *testing.T) {
293 src := `
294 name: "path/to/example.proto",
295 package: "example"
296 message_type <
297 name: "StringMessage"
298 field <
299 name: "string"
300 number: 1
301 label: LABEL_OPTIONAL
302 type: TYPE_STRING
303 >
304 >
305 service <
306 name: "ExampleService"
307 method <
308 name: "Echo"
309 input_type: "StringMessage"
310 output_type: "StringMessage"
311 >
312 >
313 `
314 var fd descriptorpb.FileDescriptorProto
315 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
316 t.Fatalf("prototext.Unmarshal (%s, &fd) failed with %v; want success", src, err)
317 }
318 msg := &Message{
319 DescriptorProto: fd.MessageType[0],
320 Fields: []*Field{
321 {
322 FieldDescriptorProto: fd.MessageType[0].Field[0],
323 },
324 },
325 }
326 file := &File{
327 FileDescriptorProto: &fd,
328 GoPkg: GoPackage{
329 Path: "path/to/example.pb",
330 Name: "example_pb",
331 },
332 Messages: []*Message{msg},
333 Services: []*Service{
334 {
335 ServiceDescriptorProto: fd.Service[0],
336 Methods: []*Method{
337 {
338 MethodDescriptorProto: fd.Service[0].Method[0],
339 RequestType: msg,
340 ResponseType: msg,
341 Bindings: []*Binding{
342 {
343 PathTmpl: compilePath(t, "/example.ExampleService/Echo"),
344 HTTPMethod: "POST",
345 Body: &Body{FieldPath: nil},
346 },
347 },
348 },
349 },
350 },
351 },
352 }
353
354 crossLinkFixture(file)
355 reg := NewRegistry()
356 reg.SetGenerateUnboundMethods(true)
357 testExtractServicesWithRegistry(t, reg, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
358 }
359
360 func TestExtractServicesCrossPackage(t *testing.T) {
361 srcs := []string{
362 `
363 name: "path/to/example.proto",
364 package: "example"
365 message_type <
366 name: "StringMessage"
367 field <
368 name: "string"
369 number: 1
370 label: LABEL_OPTIONAL
371 type: TYPE_STRING
372 >
373 >
374 service <
375 name: "ExampleService"
376 method <
377 name: "ToString"
378 input_type: ".another.example.BoolMessage"
379 output_type: "StringMessage"
380 options <
381 [google.api.http] <
382 post: "/v1/example/to_s"
383 body: "*"
384 >
385 >
386 >
387 >
388 `, `
389 name: "path/to/another/example.proto",
390 package: "another.example"
391 message_type <
392 name: "BoolMessage"
393 field <
394 name: "bool"
395 number: 1
396 label: LABEL_OPTIONAL
397 type: TYPE_BOOL
398 >
399 >
400 `,
401 }
402 var fds []*descriptorpb.FileDescriptorProto
403 for _, src := range srcs {
404 var fd descriptorpb.FileDescriptorProto
405 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
406 t.Fatalf("prototext.Unmarshal(%s, &fd) failed with %v; want success", src, err)
407 }
408 fds = append(fds, &fd)
409 }
410 stringMsg := &Message{
411 DescriptorProto: fds[0].MessageType[0],
412 Fields: []*Field{
413 {
414 FieldDescriptorProto: fds[0].MessageType[0].Field[0],
415 },
416 },
417 }
418 boolMsg := &Message{
419 DescriptorProto: fds[1].MessageType[0],
420 Fields: []*Field{
421 {
422 FieldDescriptorProto: fds[1].MessageType[0].Field[0],
423 },
424 },
425 }
426 files := []*File{
427 {
428 FileDescriptorProto: fds[0],
429 GoPkg: GoPackage{
430 Path: "path/to/example.pb",
431 Name: "example_pb",
432 },
433 Messages: []*Message{stringMsg},
434 Services: []*Service{
435 {
436 ServiceDescriptorProto: fds[0].Service[0],
437 Methods: []*Method{
438 {
439 MethodDescriptorProto: fds[0].Service[0].Method[0],
440 RequestType: boolMsg,
441 ResponseType: stringMsg,
442 Bindings: []*Binding{
443 {
444 PathTmpl: compilePath(t, "/v1/example/to_s"),
445 HTTPMethod: "POST",
446 Body: &Body{FieldPath: nil},
447 },
448 },
449 },
450 },
451 },
452 },
453 },
454 {
455 FileDescriptorProto: fds[1],
456 GoPkg: GoPackage{
457 Path: "path/to/another/example.pb",
458 Name: "example_pb",
459 },
460 Messages: []*Message{boolMsg},
461 },
462 }
463
464 for _, file := range files {
465 crossLinkFixture(file)
466 }
467 testExtractServices(t, fds, "path/to/example.proto", files[0].Services)
468 }
469
470 func TestExtractServicesWithBodyPath(t *testing.T) {
471 src := `
472 name: "path/to/example.proto",
473 package: "example"
474 message_type <
475 name: "OuterMessage"
476 nested_type <
477 name: "StringMessage"
478 field <
479 name: "string"
480 number: 1
481 label: LABEL_OPTIONAL
482 type: TYPE_STRING
483 >
484 >
485 field <
486 name: "nested"
487 number: 1
488 label: LABEL_OPTIONAL
489 type: TYPE_MESSAGE
490 type_name: "StringMessage"
491 >
492 >
493 service <
494 name: "ExampleService"
495 method <
496 name: "Echo"
497 input_type: "OuterMessage"
498 output_type: "OuterMessage"
499 options <
500 [google.api.http] <
501 post: "/v1/example/echo"
502 body: "nested"
503 >
504 >
505 >
506 >
507 `
508 var fd descriptorpb.FileDescriptorProto
509 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
510 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
511 }
512 msg := &Message{
513 DescriptorProto: fd.MessageType[0],
514 Fields: []*Field{
515 {
516 FieldDescriptorProto: fd.MessageType[0].Field[0],
517 },
518 },
519 }
520 file := &File{
521 FileDescriptorProto: &fd,
522 GoPkg: GoPackage{
523 Path: "path/to/example.pb",
524 Name: "example_pb",
525 },
526 Messages: []*Message{msg},
527 Services: []*Service{
528 {
529 ServiceDescriptorProto: fd.Service[0],
530 Methods: []*Method{
531 {
532 MethodDescriptorProto: fd.Service[0].Method[0],
533 RequestType: msg,
534 ResponseType: msg,
535 Bindings: []*Binding{
536 {
537 PathTmpl: compilePath(t, "/v1/example/echo"),
538 HTTPMethod: "POST",
539 Body: &Body{
540 FieldPath: FieldPath{
541 {
542 Name: "nested",
543 Target: msg.Fields[0],
544 },
545 },
546 },
547 },
548 },
549 },
550 },
551 },
552 },
553 }
554
555 crossLinkFixture(file)
556 testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
557 }
558
559 func TestExtractServicesWithPathParam(t *testing.T) {
560 src := `
561 name: "path/to/example.proto",
562 package: "example"
563 message_type <
564 name: "StringMessage"
565 field <
566 name: "string"
567 number: 1
568 label: LABEL_OPTIONAL
569 type: TYPE_STRING
570 >
571 >
572 service <
573 name: "ExampleService"
574 method <
575 name: "Echo"
576 input_type: "StringMessage"
577 output_type: "StringMessage"
578 options <
579 [google.api.http] <
580 get: "/v1/example/echo/{string=*}"
581 >
582 >
583 >
584 >
585 `
586 var fd descriptorpb.FileDescriptorProto
587 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
588 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
589 }
590 msg := &Message{
591 DescriptorProto: fd.MessageType[0],
592 Fields: []*Field{
593 {
594 FieldDescriptorProto: fd.MessageType[0].Field[0],
595 },
596 },
597 }
598 file := &File{
599 FileDescriptorProto: &fd,
600 GoPkg: GoPackage{
601 Path: "path/to/example.pb",
602 Name: "example_pb",
603 },
604 Messages: []*Message{msg},
605 Services: []*Service{
606 {
607 ServiceDescriptorProto: fd.Service[0],
608 Methods: []*Method{
609 {
610 MethodDescriptorProto: fd.Service[0].Method[0],
611 RequestType: msg,
612 ResponseType: msg,
613 Bindings: []*Binding{
614 {
615 PathTmpl: compilePath(t, "/v1/example/echo/{string=*}"),
616 HTTPMethod: "GET",
617 PathParams: []Parameter{
618 {
619 FieldPath: FieldPath{
620 {
621 Name: "string",
622 Target: msg.Fields[0],
623 },
624 },
625 Target: msg.Fields[0],
626 },
627 },
628 },
629 },
630 },
631 },
632 },
633 },
634 }
635
636 crossLinkFixture(file)
637 testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
638 }
639
640 func TestExtractServicesWithAdditionalBinding(t *testing.T) {
641 src := `
642 name: "path/to/example.proto",
643 package: "example"
644 message_type <
645 name: "StringMessage"
646 field <
647 name: "string"
648 number: 1
649 label: LABEL_OPTIONAL
650 type: TYPE_STRING
651 >
652 >
653 service <
654 name: "ExampleService"
655 method <
656 name: "Echo"
657 input_type: "StringMessage"
658 output_type: "StringMessage"
659 options <
660 [google.api.http] <
661 post: "/v1/example/echo"
662 body: "*"
663 additional_bindings <
664 get: "/v1/example/echo/{string}"
665 >
666 additional_bindings <
667 post: "/v2/example/echo"
668 body: "string"
669 >
670 >
671 >
672 >
673 >
674 `
675 var fd descriptorpb.FileDescriptorProto
676 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
677 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
678 }
679 msg := &Message{
680 DescriptorProto: fd.MessageType[0],
681 Fields: []*Field{
682 {
683 FieldDescriptorProto: fd.MessageType[0].Field[0],
684 },
685 },
686 }
687 file := &File{
688 FileDescriptorProto: &fd,
689 GoPkg: GoPackage{
690 Path: "path/to/example.pb",
691 Name: "example_pb",
692 },
693 Messages: []*Message{msg},
694 Services: []*Service{
695 {
696 ServiceDescriptorProto: fd.Service[0],
697 Methods: []*Method{
698 {
699 MethodDescriptorProto: fd.Service[0].Method[0],
700 RequestType: msg,
701 ResponseType: msg,
702 Bindings: []*Binding{
703 {
704 Index: 0,
705 PathTmpl: compilePath(t, "/v1/example/echo"),
706 HTTPMethod: "POST",
707 Body: &Body{FieldPath: nil},
708 },
709 {
710 Index: 1,
711 PathTmpl: compilePath(t, "/v1/example/echo/{string}"),
712 HTTPMethod: "GET",
713 PathParams: []Parameter{
714 {
715 FieldPath: FieldPath{
716 {
717 Name: "string",
718 Target: msg.Fields[0],
719 },
720 },
721 Target: msg.Fields[0],
722 },
723 },
724 Body: nil,
725 },
726 {
727 Index: 2,
728 PathTmpl: compilePath(t, "/v2/example/echo"),
729 HTTPMethod: "POST",
730 Body: &Body{
731 FieldPath: FieldPath{
732 FieldPathComponent{
733 Name: "string",
734 Target: msg.Fields[0],
735 },
736 },
737 },
738 },
739 },
740 },
741 },
742 },
743 },
744 }
745
746 crossLinkFixture(file)
747 testExtractServices(t, []*descriptorpb.FileDescriptorProto{&fd}, "path/to/example.proto", file.Services)
748 }
749
750 func TestExtractServicesWithError(t *testing.T) {
751 for _, spec := range []struct {
752 target string
753 srcs []string
754 }{
755 {
756 target: "path/to/example.proto",
757 srcs: []string{
758
759 `
760 name: "path/to/example.proto",
761 package: "example"
762 service <
763 name: "ExampleService"
764 method <
765 name: "Echo"
766 input_type: "StringMessage"
767 output_type: "StringMessage"
768 options <
769 [google.api.http] <
770 post: "/v1/example/echo"
771 body: "*"
772 >
773 >
774 >
775 >
776 `,
777 },
778 },
779
780 {
781 target: "path/to/example.proto",
782 srcs: []string{`
783 name: "path/to/example.proto",
784 package: "example"
785 message_type <
786 name: "StringMessage"
787 field <
788 name: "string"
789 number: 1
790 label: LABEL_OPTIONAL
791 type: TYPE_STRING
792 >
793 >
794 service <
795 name: "ExampleService"
796 method <
797 name: "Echo"
798 input_type: "StringMessage"
799 output_type: "StringMessage"
800 options <
801 [google.api.http] <
802 post: "/v1/example/echo"
803 body: "bool"
804 >
805 >
806 >
807 >`,
808 },
809 },
810
811 {
812 target: "path/to/example.proto",
813 srcs: []string{
814 `
815 name: "path/to/example.proto",
816 package: "example"
817 message_type <
818 name: "StringMessage"
819 field <
820 name: "string"
821 number: 1
822 label: LABEL_OPTIONAL
823 type: TYPE_STRING
824 >
825 >
826 service <
827 name: "ExampleService"
828 method <
829 name: "Echo"
830 input_type: "StringMessage"
831 output_type: "StringMessage"
832 options <
833 [google.api.http] <
834 post: "/v1/example/echo/{bool=*}"
835 >
836 >
837 >
838 >
839 `,
840 },
841 },
842
843 {
844 target: "path/to/example.proto",
845 srcs: []string{
846 `
847 name: "path/to/example.proto",
848 package: "example"
849 message_type <
850 name: "OuterMessage"
851 field <
852 name: "mid"
853 number: 1
854 label: LABEL_OPTIONAL
855 type: TYPE_STRING
856 >
857 field <
858 name: "bool"
859 number: 2
860 label: LABEL_OPTIONAL
861 type: TYPE_BOOL
862 >
863 >
864 service <
865 name: "ExampleService"
866 method <
867 name: "Echo"
868 input_type: "OuterMessage"
869 output_type: "OuterMessage"
870 options <
871 [google.api.http] <
872 post: "/v1/example/echo/{mid.bool=*}"
873 >
874 >
875 >
876 >
877 `,
878 },
879 },
880
881 {
882 target: "path/to/example.proto",
883 srcs: []string{
884 `
885 name: "path/to/example.proto",
886 package: "example"
887 message_type <
888 name: "StringMessage"
889 field <
890 name: "string"
891 number: 1
892 label: LABEL_OPTIONAL
893 type: TYPE_STRING
894 >
895 >
896 service <
897 name: "ExampleService"
898 method <
899 name: "Echo"
900 input_type: "StringMessage"
901 output_type: "StringMessage"
902 options <
903 [google.api.http] <
904 post: "/v1/example/echo/{bool=*}"
905 >
906 >
907 client_streaming: true
908 >
909 >
910 `,
911 },
912 },
913
914 {
915 target: "path/to/example.proto",
916 srcs: []string{
917 `
918 name: "path/to/example.proto",
919 package: "example"
920 message_type <
921 name: "StringMessage"
922 field <
923 name: "string"
924 number: 1
925 label: LABEL_OPTIONAL
926 type: TYPE_STRING
927 >
928 >
929 service <
930 name: "ExampleService"
931 method <
932 name: "Echo"
933 input_type: "StringMessage"
934 output_type: "StringMessage"
935 options <
936 [google.api.http] <
937 get: "/v1/example/echo"
938 body: "string"
939 >
940 >
941 >
942 >
943 `,
944 },
945 },
946
947 {
948 target: "path/to/example.proto",
949 srcs: []string{
950 `
951 name: "path/to/example.proto",
952 package: "example"
953 message_type <
954 name: "StringMessage"
955 field <
956 name: "string"
957 number: 1
958 label: LABEL_OPTIONAL
959 type: TYPE_STRING
960 >
961 >
962 service <
963 name: "ExampleService"
964 method <
965 name: "RemoveResource"
966 input_type: "StringMessage"
967 output_type: "StringMessage"
968 options <
969 [google.api.http] <
970 delete: "/v1/example/resource"
971 body: "string"
972 >
973 >
974 >
975 >
976 `,
977 },
978 },
979
980 {
981 target: "path/to/example.proto",
982 srcs: []string{
983 `
984 name: "path/to/example.proto",
985 package: "example"
986 service <
987 name: "ExampleService"
988 method <
989 name: "RemoveResource"
990 input_type: "StringMessage"
991 output_type: "StringMessage"
992 options <
993 [google.api.http] <
994 body: "string"
995 >
996 >
997 >
998 >
999 `,
1000 },
1001 },
1002
1003 {
1004 target: "path/to/example.proto",
1005 srcs: []string{`
1006 name: "path/to/example.proto",
1007 package: "example"
1008 message_type <
1009 name: "OuterMessage"
1010 nested_type <
1011 name: "StringMessage"
1012 field <
1013 name: "value"
1014 number: 1
1015 label: LABEL_OPTIONAL
1016 type: TYPE_STRING
1017 >
1018 >
1019 field <
1020 name: "string"
1021 number: 1
1022 label: LABEL_OPTIONAL
1023 type: TYPE_MESSAGE
1024 type_name: "StringMessage"
1025 >
1026 >
1027 service <
1028 name: "ExampleService"
1029 method <
1030 name: "Echo"
1031 input_type: "OuterMessage"
1032 output_type: "OuterMessage"
1033 options <
1034 [google.api.http] <
1035 get: "/v1/example/echo/{string=*}"
1036 >
1037 >
1038 >
1039 >
1040 `,
1041 },
1042 },
1043 } {
1044 reg := NewRegistry()
1045
1046 for _, src := range spec.srcs {
1047 var fd descriptorpb.FileDescriptorProto
1048 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
1049 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
1050 }
1051 reg.loadFile(spec.target, &protogen.File{
1052 Proto: &fd,
1053 })
1054 }
1055 err := reg.loadServices(reg.files[spec.target])
1056 if err == nil {
1057 t.Errorf("loadServices(%q) succeeded; want an error; files=%v", spec.target, spec.srcs)
1058 }
1059 t.Log(err)
1060 }
1061 }
1062
1063 func TestResolveFieldPath(t *testing.T) {
1064 for _, spec := range []struct {
1065 src string
1066 path string
1067 wantErr bool
1068 }{
1069 {
1070 src: `
1071 name: 'example.proto'
1072 package: 'example'
1073 message_type <
1074 name: 'ExampleMessage'
1075 field <
1076 name: 'string'
1077 type: TYPE_STRING
1078 label: LABEL_OPTIONAL
1079 number: 1
1080 >
1081 >
1082 `,
1083 path: "string",
1084 wantErr: false,
1085 },
1086
1087 {
1088 src: `
1089 name: 'example.proto'
1090 package: 'example'
1091 message_type <
1092 name: 'ExampleMessage'
1093 field <
1094 name: 'string'
1095 type: TYPE_STRING
1096 label: LABEL_OPTIONAL
1097 number: 1
1098 >
1099 >
1100 `,
1101 path: "something_else",
1102 wantErr: true,
1103 },
1104
1105 {
1106 src: `
1107 name: 'example.proto'
1108 package: 'example'
1109 message_type <
1110 name: 'ExampleMessage'
1111 field <
1112 name: 'string'
1113 type: TYPE_STRING
1114 label: LABEL_REPEATED
1115 number: 1
1116 >
1117 >
1118 `,
1119 path: "string",
1120 wantErr: false,
1121 },
1122
1123 {
1124 src: `
1125 name: 'example.proto'
1126 package: 'example'
1127 message_type <
1128 name: 'ExampleMessage'
1129 field <
1130 name: 'nested'
1131 type: TYPE_MESSAGE
1132 type_name: 'AnotherMessage'
1133 label: LABEL_OPTIONAL
1134 number: 1
1135 >
1136 field <
1137 name: 'terminal'
1138 type: TYPE_BOOL
1139 label: LABEL_OPTIONAL
1140 number: 2
1141 >
1142 >
1143 message_type <
1144 name: 'AnotherMessage'
1145 field <
1146 name: 'nested2'
1147 type: TYPE_MESSAGE
1148 type_name: 'ExampleMessage'
1149 label: LABEL_OPTIONAL
1150 number: 1
1151 >
1152 >
1153 `,
1154 path: "nested.nested2.nested.nested2.nested.nested2.terminal",
1155 wantErr: false,
1156 },
1157
1158 {
1159 src: `
1160 name: 'example.proto'
1161 package: 'example'
1162 message_type <
1163 name: 'ExampleMessage'
1164 field <
1165 name: 'nested'
1166 type: TYPE_MESSAGE
1167 type_name: 'AnotherMessage'
1168 label: LABEL_OPTIONAL
1169 number: 1
1170 >
1171 field <
1172 name: 'terminal'
1173 type: TYPE_BOOL
1174 label: LABEL_OPTIONAL
1175 number: 2
1176 >
1177 >
1178 message_type <
1179 name: 'AnotherMessage'
1180 field <
1181 name: 'nested2'
1182 type: TYPE_MESSAGE
1183 type_name: 'ExampleMessage'
1184 label: LABEL_OPTIONAL
1185 number: 1
1186 >
1187 >
1188 `,
1189 path: "nested.terminal.nested2",
1190 wantErr: true,
1191 },
1192
1193 {
1194 src: `
1195 name: 'example.proto'
1196 package: 'example'
1197 message_type <
1198 name: 'ExampleMessage'
1199 field <
1200 name: 'nested'
1201 type: TYPE_MESSAGE
1202 type_name: 'AnotherMessage'
1203 label: LABEL_OPTIONAL
1204 number: 1
1205 >
1206 field <
1207 name: 'terminal'
1208 type: TYPE_BOOL
1209 label: LABEL_OPTIONAL
1210 number: 2
1211 >
1212 >
1213 message_type <
1214 name: 'AnotherMessage'
1215 field <
1216 name: 'nested2'
1217 type: TYPE_MESSAGE
1218 type_name: 'ExampleMessage'
1219 label: LABEL_REPEATED
1220 number: 1
1221 >
1222 >
1223 `,
1224 path: "nested.nested2.terminal",
1225 wantErr: false,
1226 },
1227 } {
1228 var file descriptorpb.FileDescriptorProto
1229 if err := prototext.Unmarshal([]byte(spec.src), &file); err != nil {
1230 t.Fatalf("proto.Unmarshal(%s) failed with %v; want success", spec.src, err)
1231 }
1232 reg := NewRegistry()
1233 reg.loadFile(file.GetName(), &protogen.File{
1234 Proto: &file,
1235 })
1236 f, err := reg.LookupFile(file.GetName())
1237 if err != nil {
1238 t.Fatalf("reg.LookupFile(%q) failed with %v; want success; on file=%s", file.GetName(), err, spec.src)
1239 }
1240 _, err = reg.resolveFieldPath(f.Messages[0], spec.path, false)
1241 if got, want := err != nil, spec.wantErr; got != want {
1242 if want {
1243 t.Errorf("reg.resolveFiledPath(%q, %q) succeeded; want an error", f.Messages[0].GetName(), spec.path)
1244 continue
1245 }
1246 t.Errorf("reg.resolveFiledPath(%q, %q) failed with %v; want success", f.Messages[0].GetName(), spec.path, err)
1247 }
1248 }
1249 }
1250
1251 func TestExtractServicesWithDeleteBody(t *testing.T) {
1252 for _, spec := range []struct {
1253 allowDeleteBody bool
1254 expectErr bool
1255 target string
1256 srcs []string
1257 }{
1258
1259 {
1260 allowDeleteBody: true,
1261 expectErr: false,
1262 target: "path/to/example.proto",
1263 srcs: []string{
1264 `
1265 name: "path/to/example.proto",
1266 package: "example"
1267 message_type <
1268 name: "StringMessage"
1269 field <
1270 name: "string"
1271 number: 1
1272 label: LABEL_OPTIONAL
1273 type: TYPE_STRING
1274 >
1275 >
1276 service <
1277 name: "ExampleService"
1278 method <
1279 name: "RemoveResource"
1280 input_type: "StringMessage"
1281 output_type: "StringMessage"
1282 options <
1283 [google.api.http] <
1284 delete: "/v1/example/resource"
1285 body: "string"
1286 >
1287 >
1288 >
1289 >
1290 `,
1291 },
1292 },
1293
1294 {
1295 allowDeleteBody: false,
1296 expectErr: true,
1297 target: "path/to/example.proto",
1298 srcs: []string{
1299 `
1300 name: "path/to/example.proto",
1301 package: "example"
1302 message_type <
1303 name: "StringMessage"
1304 field <
1305 name: "string"
1306 number: 1
1307 label: LABEL_OPTIONAL
1308 type: TYPE_STRING
1309 >
1310 >
1311 service <
1312 name: "ExampleService"
1313 method <
1314 name: "RemoveResource"
1315 input_type: "StringMessage"
1316 output_type: "StringMessage"
1317 options <
1318 [google.api.http] <
1319 delete: "/v1/example/resource"
1320 body: "string"
1321 >
1322 >
1323 >
1324 >
1325 `,
1326 },
1327 },
1328 } {
1329 reg := NewRegistry()
1330 reg.SetAllowDeleteBody(spec.allowDeleteBody)
1331
1332 for _, src := range spec.srcs {
1333 var fd descriptorpb.FileDescriptorProto
1334 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
1335 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
1336 }
1337 reg.loadFile(fd.GetName(), &protogen.File{
1338 Proto: &fd,
1339 })
1340 }
1341 err := reg.loadServices(reg.files[spec.target])
1342 if spec.expectErr && err == nil {
1343 t.Errorf("loadServices(%q) succeeded; want an error; allowDeleteBody=%v, files=%v", spec.target, spec.allowDeleteBody, spec.srcs)
1344 }
1345 if !spec.expectErr && err != nil {
1346 t.Errorf("loadServices(%q) failed; do not want an error; allowDeleteBody=%v, files=%v", spec.target, spec.allowDeleteBody, spec.srcs)
1347 }
1348 t.Log(err)
1349 }
1350 }
1351
1352 func TestCauseErrorWithPathParam(t *testing.T) {
1353 src := `
1354 name: "path/to/example.proto",
1355 package: "example"
1356 message_type <
1357 name: "TypeMessage"
1358 field <
1359 name: "message"
1360 type: TYPE_MESSAGE
1361 type_name: 'ExampleMessage'
1362 number: 1,
1363 label: LABEL_OPTIONAL
1364 >
1365 >
1366 service <
1367 name: "ExampleService"
1368 method <
1369 name: "Echo"
1370 input_type: "TypeMessage"
1371 output_type: "TypeMessage"
1372 options <
1373 [google.api.http] <
1374 get: "/v1/example/echo/{message=*}"
1375 >
1376 >
1377 >
1378 >
1379 `
1380 var fd descriptorpb.FileDescriptorProto
1381 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
1382 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
1383 }
1384 target := "path/to/example.proto"
1385 reg := NewRegistry()
1386 input := []*descriptorpb.FileDescriptorProto{&fd}
1387 reg.loadFile(fd.GetName(), &protogen.File{
1388 Proto: &fd,
1389 })
1390
1391 wantErr := true
1392 err := reg.loadServices(reg.files[target])
1393 if got, want := err != nil, wantErr; got != want {
1394 if want {
1395 t.Errorf("loadServices(%q, %q) succeeded; want an error", target, input)
1396 }
1397 t.Errorf("loadServices(%q, %q) failed with %v; want success", target, input, err)
1398 }
1399 }
1400
1401 func TestOptionalProto3URLPathMappingError(t *testing.T) {
1402 src := `
1403 name: "path/to/example.proto"
1404 package: "example"
1405 message_type <
1406 name: "StringMessage"
1407 field <
1408 name: "field1"
1409 number: 1
1410 type: TYPE_STRING
1411 proto3_optional: true
1412 >
1413 >
1414 service <
1415 name: "ExampleService"
1416 method <
1417 name: "Echo"
1418 input_type: "StringMessage"
1419 output_type: "StringMessage"
1420 options <
1421 [google.api.http] <
1422 get: "/v1/example/echo/{field1=*}"
1423 >
1424 >
1425 >
1426 >
1427 `
1428 var fd descriptorpb.FileDescriptorProto
1429 if err := prototext.Unmarshal([]byte(src), &fd); err != nil {
1430 t.Fatalf("proto.UnmarshalText(%s, &fd) failed with %v; want success", src, err)
1431 }
1432 target := "path/to/example.proto"
1433 reg := NewRegistry()
1434 input := []*descriptorpb.FileDescriptorProto{&fd}
1435 reg.loadFile(fd.GetName(), &protogen.File{
1436 Proto: &fd,
1437 })
1438 wantErrMsg := "field not allowed in field path: field1 in field1"
1439 err := reg.loadServices(reg.files[target])
1440 if err != nil {
1441 if !strings.Contains(err.Error(), wantErrMsg) {
1442 t.Errorf("loadServices(%q, %q) failed with %v; want %s", target, input, err, wantErrMsg)
1443 }
1444 } else {
1445 t.Errorf("loadServices(%q, %q) expcted an error %s, got nil", target, input, wantErrMsg)
1446 }
1447 }
1448
View as plain text