github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/internal/descriptor/services_test.go (about) 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 // message not found 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 // body field path not resolved 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 // param field path not resolved 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 // non aggregate type on field path 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 // path param in client streaming 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 // body for GET 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 // body for DELETE 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 // no pattern specified 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 // unsupported path parameter type 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 // no such field 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 // repeated field 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 // nested field 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 // non aggregate field on the path 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 // repeated field 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 // body for DELETE, but registry configured to allow it 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 // body for DELETE, registry configured not to allow it 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 // switch this field to see the error 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 }