github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/internal/genopenapi/template_test.go (about) 1 package genopenapi 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math" 9 "os" 10 "reflect" 11 "strings" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor" 16 "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor/openapiconfig" 17 "github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule" 18 openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" 19 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 20 "google.golang.org/genproto/googleapis/api/annotations" 21 "google.golang.org/genproto/googleapis/api/visibility" 22 "google.golang.org/protobuf/encoding/protojson" 23 "google.golang.org/protobuf/proto" 24 "google.golang.org/protobuf/reflect/protodesc" 25 "google.golang.org/protobuf/reflect/protoreflect" 26 "google.golang.org/protobuf/reflect/protoregistry" 27 "google.golang.org/protobuf/types/descriptorpb" 28 "google.golang.org/protobuf/types/known/anypb" 29 "google.golang.org/protobuf/types/known/durationpb" 30 "google.golang.org/protobuf/types/known/emptypb" 31 field_mask "google.golang.org/protobuf/types/known/fieldmaskpb" 32 "google.golang.org/protobuf/types/known/structpb" 33 "google.golang.org/protobuf/types/known/timestamppb" 34 "google.golang.org/protobuf/types/known/wrapperspb" 35 "google.golang.org/protobuf/types/pluginpb" 36 ) 37 38 var marshaler = &runtime.JSONPb{} 39 40 func TestOpenapiExamplesFromProtoExamples(t *testing.T) { 41 examples := openapiExamplesFromProtoExamples(map[string]string{ 42 "application/json": `{"Hello": "Worldr!"}`, 43 "plain/text": "Hello, World!", 44 }) 45 46 testCases := map[Format]string{ 47 FormatJSON: ` 48 { 49 "application/json": { 50 "Hello": "Worldr!" 51 }, 52 "plain/text": "Hello, World!" 53 } 54 `, 55 FormatYAML: ` 56 application/json: 57 Hello: Worldr! 58 plain/text: Hello, World! 59 `, 60 } 61 62 spaceRemover := strings.NewReplacer(" ", "", "\t", "", "\n", "") 63 64 for format, expected := range testCases { 65 t.Run(string(format), func(t *testing.T) { 66 var buf bytes.Buffer 67 68 encoder, err := format.NewEncoder(&buf) 69 if err != nil { 70 t.Fatalf("creating encoder: %s", err) 71 } 72 73 err = encoder.Encode(examples) 74 if err != nil { 75 t.Fatalf("encoding: %s", err) 76 } 77 78 actual := spaceRemover.Replace(buf.String()) 79 expected = spaceRemover.Replace(expected) 80 81 if expected != actual { 82 t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual) 83 } 84 }) 85 } 86 } 87 88 func crossLinkFixture(f *descriptor.File) *descriptor.File { 89 for _, m := range f.Messages { 90 m.File = f 91 } 92 for _, svc := range f.Services { 93 svc.File = f 94 for _, m := range svc.Methods { 95 m.Service = svc 96 for _, b := range m.Bindings { 97 b.Method = m 98 for _, param := range b.PathParams { 99 param.Method = m 100 } 101 } 102 } 103 } 104 return f 105 } 106 107 func reqFromFile(f *descriptor.File) *pluginpb.CodeGeneratorRequest { 108 return &pluginpb.CodeGeneratorRequest{ 109 ProtoFile: []*descriptorpb.FileDescriptorProto{ 110 f.FileDescriptorProto, 111 }, 112 FileToGenerate: []string{f.GetName()}, 113 } 114 } 115 116 func TestMessageToQueryParametersWithEnumAsInt(t *testing.T) { 117 type test struct { 118 MsgDescs []*descriptorpb.DescriptorProto 119 Message string 120 Params []openapiParameterObject 121 } 122 123 tests := []test{ 124 { 125 MsgDescs: []*descriptorpb.DescriptorProto{ 126 { 127 Name: proto.String("ExampleMessage"), 128 Field: []*descriptorpb.FieldDescriptorProto{ 129 { 130 Name: proto.String("a"), 131 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 132 Number: proto.Int32(1), 133 }, 134 { 135 Name: proto.String("b"), 136 Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(), 137 Number: proto.Int32(2), 138 }, 139 { 140 Name: proto.String("c"), 141 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 142 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 143 Number: proto.Int32(3), 144 }, 145 }, 146 }, 147 }, 148 Message: "ExampleMessage", 149 Params: []openapiParameterObject{ 150 { 151 Name: "a", 152 In: "query", 153 Required: false, 154 Type: "string", 155 }, 156 { 157 Name: "b", 158 In: "query", 159 Required: false, 160 Type: "number", 161 Format: "double", 162 }, 163 { 164 Name: "c", 165 In: "query", 166 Required: false, 167 Type: "array", 168 CollectionFormat: "multi", 169 }, 170 }, 171 }, 172 { 173 MsgDescs: []*descriptorpb.DescriptorProto{ 174 { 175 Name: proto.String("ExampleMessage"), 176 Field: []*descriptorpb.FieldDescriptorProto{ 177 { 178 Name: proto.String("nested"), 179 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 180 TypeName: proto.String(".example.Nested"), 181 Number: proto.Int32(1), 182 }, 183 }, 184 }, 185 { 186 Name: proto.String("Nested"), 187 Field: []*descriptorpb.FieldDescriptorProto{ 188 { 189 Name: proto.String("a"), 190 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 191 Number: proto.Int32(1), 192 }, 193 { 194 Name: proto.String("deep"), 195 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 196 TypeName: proto.String(".example.Nested.DeepNested"), 197 Number: proto.Int32(2), 198 }, 199 }, 200 NestedType: []*descriptorpb.DescriptorProto{{ 201 Name: proto.String("DeepNested"), 202 Field: []*descriptorpb.FieldDescriptorProto{ 203 { 204 Name: proto.String("b"), 205 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 206 Number: proto.Int32(1), 207 }, 208 { 209 Name: proto.String("c"), 210 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 211 TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"), 212 Number: proto.Int32(2), 213 }, 214 }, 215 EnumType: []*descriptorpb.EnumDescriptorProto{ 216 { 217 Name: proto.String("DeepEnum"), 218 Value: []*descriptorpb.EnumValueDescriptorProto{ 219 {Name: proto.String("FALSE"), Number: proto.Int32(0)}, 220 {Name: proto.String("TRUE"), Number: proto.Int32(1)}, 221 }, 222 }, 223 }, 224 }}, 225 }, 226 }, 227 Message: "ExampleMessage", 228 Params: []openapiParameterObject{ 229 { 230 Name: "nested.a", 231 In: "query", 232 Required: false, 233 Type: "string", 234 }, 235 { 236 Name: "nested.deep.b", 237 In: "query", 238 Required: false, 239 Type: "string", 240 }, 241 { 242 Name: "nested.deep.c", 243 In: "query", 244 Required: false, 245 Type: "integer", 246 Enum: []int{0, 1}, 247 Default: 0, 248 }, 249 }, 250 }, 251 } 252 253 for _, test := range tests { 254 reg := descriptor.NewRegistry() 255 reg.SetEnumsAsInts(true) 256 var msgs []*descriptor.Message 257 for _, msgdesc := range test.MsgDescs { 258 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 259 } 260 file := descriptor.File{ 261 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 262 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 263 Name: proto.String("example.proto"), 264 Package: proto.String("example"), 265 Dependency: []string{}, 266 MessageType: test.MsgDescs, 267 Service: []*descriptorpb.ServiceDescriptorProto{}, 268 Options: &descriptorpb.FileOptions{ 269 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 270 }, 271 }, 272 GoPkg: descriptor.GoPackage{ 273 Path: "example.com/path/to/example/example.pb", 274 Name: "example_pb", 275 }, 276 Messages: msgs, 277 } 278 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 279 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 280 }) 281 if err != nil { 282 t.Fatalf("failed to load code generator request: %v", err) 283 } 284 285 message, err := reg.LookupMsg("", ".example."+test.Message) 286 if err != nil { 287 t.Fatalf("failed to lookup message: %s", err) 288 } 289 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 290 if err != nil { 291 t.Fatalf("failed to convert message to query parameters: %s", err) 292 } 293 // avoid checking Items for array types 294 for i := range params { 295 params[i].Items = nil 296 } 297 if !reflect.DeepEqual(params, test.Params) { 298 t.Errorf("expected %v, got %v", test.Params, params) 299 } 300 } 301 } 302 303 func TestMessageToQueryParametersWithOmitEnumDefaultValue(t *testing.T) { 304 type test struct { 305 MsgDescs []*descriptorpb.DescriptorProto 306 Message string 307 Params []openapiParameterObject 308 } 309 310 tests := []test{ 311 { 312 MsgDescs: []*descriptorpb.DescriptorProto{ 313 { 314 Name: proto.String("ExampleMessage"), 315 Field: []*descriptorpb.FieldDescriptorProto{ 316 { 317 Name: proto.String("a"), 318 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 319 Number: proto.Int32(1), 320 }, 321 { 322 Name: proto.String("b"), 323 Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(), 324 Number: proto.Int32(2), 325 }, 326 { 327 Name: proto.String("c"), 328 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 329 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 330 Number: proto.Int32(3), 331 }, 332 }, 333 }, 334 }, 335 Message: "ExampleMessage", 336 Params: []openapiParameterObject{ 337 { 338 Name: "a", 339 In: "query", 340 Required: false, 341 Type: "string", 342 }, 343 { 344 Name: "b", 345 In: "query", 346 Required: false, 347 Type: "number", 348 Format: "double", 349 }, 350 { 351 Name: "c", 352 In: "query", 353 Required: false, 354 Type: "array", 355 CollectionFormat: "multi", 356 }, 357 }, 358 }, 359 { 360 MsgDescs: []*descriptorpb.DescriptorProto{ 361 { 362 Name: proto.String("ExampleMessage"), 363 Field: []*descriptorpb.FieldDescriptorProto{ 364 { 365 Name: proto.String("nested"), 366 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 367 TypeName: proto.String(".example.Nested"), 368 Number: proto.Int32(1), 369 }, 370 }, 371 }, 372 { 373 Name: proto.String("Nested"), 374 Field: []*descriptorpb.FieldDescriptorProto{ 375 { 376 Name: proto.String("a"), 377 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 378 Number: proto.Int32(1), 379 }, 380 { 381 Name: proto.String("deep"), 382 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 383 TypeName: proto.String(".example.Nested.DeepNested"), 384 Number: proto.Int32(2), 385 }, 386 }, 387 NestedType: []*descriptorpb.DescriptorProto{{ 388 Name: proto.String("DeepNested"), 389 Field: []*descriptorpb.FieldDescriptorProto{ 390 { 391 Name: proto.String("b"), 392 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 393 Number: proto.Int32(1), 394 }, 395 { 396 Name: proto.String("c"), 397 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 398 TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"), 399 Number: proto.Int32(2), 400 }, 401 }, 402 EnumType: []*descriptorpb.EnumDescriptorProto{ 403 { 404 Name: proto.String("DeepEnum"), 405 Value: []*descriptorpb.EnumValueDescriptorProto{ 406 {Name: proto.String("FALSE"), Number: proto.Int32(0)}, 407 {Name: proto.String("TRUE"), Number: proto.Int32(1)}, 408 }, 409 }, 410 }, 411 }}, 412 }, 413 }, 414 Message: "ExampleMessage", 415 Params: []openapiParameterObject{ 416 { 417 Name: "nested.a", 418 In: "query", 419 Required: false, 420 Type: "string", 421 }, 422 { 423 Name: "nested.deep.b", 424 In: "query", 425 Required: false, 426 Type: "string", 427 }, 428 { 429 Name: "nested.deep.c", 430 In: "query", 431 Required: false, 432 Type: "string", 433 Enum: []string{"TRUE"}, 434 }, 435 }, 436 }, 437 } 438 439 for _, test := range tests { 440 reg := descriptor.NewRegistry() 441 reg.SetOmitEnumDefaultValue(true) 442 var msgs []*descriptor.Message 443 for _, msgdesc := range test.MsgDescs { 444 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 445 } 446 file := descriptor.File{ 447 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 448 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 449 Name: proto.String("example.proto"), 450 Package: proto.String("example"), 451 Dependency: []string{}, 452 MessageType: test.MsgDescs, 453 Service: []*descriptorpb.ServiceDescriptorProto{}, 454 Options: &descriptorpb.FileOptions{ 455 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 456 }, 457 }, 458 GoPkg: descriptor.GoPackage{ 459 Path: "example.com/path/to/example/example.pb", 460 Name: "example_pb", 461 }, 462 Messages: msgs, 463 } 464 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 465 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 466 }) 467 if err != nil { 468 t.Fatalf("failed to load code generator request: %v", err) 469 } 470 471 message, err := reg.LookupMsg("", ".example."+test.Message) 472 if err != nil { 473 t.Fatalf("failed to lookup message: %s", err) 474 } 475 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 476 if err != nil { 477 t.Fatalf("failed to convert message to query parameters: %s", err) 478 } 479 // avoid checking Items for array types 480 for i := range params { 481 params[i].Items = nil 482 } 483 if !reflect.DeepEqual(params, test.Params) { 484 t.Errorf("expected %v, got %v", test.Params, params) 485 } 486 } 487 } 488 489 func TestMessageToQueryParameters(t *testing.T) { 490 type test struct { 491 MsgDescs []*descriptorpb.DescriptorProto 492 Message string 493 Params []openapiParameterObject 494 } 495 496 tests := []test{ 497 { 498 MsgDescs: []*descriptorpb.DescriptorProto{ 499 { 500 Name: proto.String("ExampleMessage"), 501 Field: []*descriptorpb.FieldDescriptorProto{ 502 { 503 Name: proto.String("a"), 504 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 505 Number: proto.Int32(1), 506 }, 507 { 508 Name: proto.String("b"), 509 Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(), 510 Number: proto.Int32(2), 511 }, 512 { 513 Name: proto.String("c"), 514 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 515 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 516 Number: proto.Int32(3), 517 }, 518 }, 519 }, 520 }, 521 Message: "ExampleMessage", 522 Params: []openapiParameterObject{ 523 { 524 Name: "a", 525 In: "query", 526 Required: false, 527 Type: "string", 528 }, 529 { 530 Name: "b", 531 In: "query", 532 Required: false, 533 Type: "number", 534 Format: "double", 535 }, 536 { 537 Name: "c", 538 In: "query", 539 Required: false, 540 Type: "array", 541 CollectionFormat: "multi", 542 }, 543 }, 544 }, 545 { 546 MsgDescs: []*descriptorpb.DescriptorProto{ 547 { 548 Name: proto.String("ExampleMessage"), 549 Field: []*descriptorpb.FieldDescriptorProto{ 550 { 551 Name: proto.String("nested"), 552 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 553 TypeName: proto.String(".example.Nested"), 554 Number: proto.Int32(1), 555 }, 556 }, 557 }, 558 { 559 Name: proto.String("Nested"), 560 Field: []*descriptorpb.FieldDescriptorProto{ 561 { 562 Name: proto.String("a"), 563 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 564 Number: proto.Int32(1), 565 }, 566 { 567 Name: proto.String("deep"), 568 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 569 TypeName: proto.String(".example.Nested.DeepNested"), 570 Number: proto.Int32(2), 571 }, 572 }, 573 NestedType: []*descriptorpb.DescriptorProto{{ 574 Name: proto.String("DeepNested"), 575 Field: []*descriptorpb.FieldDescriptorProto{ 576 { 577 Name: proto.String("b"), 578 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 579 Number: proto.Int32(1), 580 }, 581 { 582 Name: proto.String("c"), 583 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 584 TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"), 585 Number: proto.Int32(2), 586 }, 587 }, 588 EnumType: []*descriptorpb.EnumDescriptorProto{ 589 { 590 Name: proto.String("DeepEnum"), 591 Value: []*descriptorpb.EnumValueDescriptorProto{ 592 {Name: proto.String("FALSE"), Number: proto.Int32(0)}, 593 {Name: proto.String("TRUE"), Number: proto.Int32(1)}, 594 }, 595 }, 596 }, 597 }}, 598 }, 599 }, 600 Message: "ExampleMessage", 601 Params: []openapiParameterObject{ 602 { 603 Name: "nested.a", 604 In: "query", 605 Required: false, 606 Type: "string", 607 }, 608 { 609 Name: "nested.deep.b", 610 In: "query", 611 Required: false, 612 Type: "string", 613 }, 614 { 615 Name: "nested.deep.c", 616 In: "query", 617 Required: false, 618 Type: "string", 619 Enum: []string{"FALSE", "TRUE"}, 620 Default: "FALSE", 621 }, 622 }, 623 }, 624 } 625 626 for _, test := range tests { 627 reg := descriptor.NewRegistry() 628 msgs := []*descriptor.Message{} 629 for _, msgdesc := range test.MsgDescs { 630 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 631 } 632 file := descriptor.File{ 633 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 634 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 635 Name: proto.String("example.proto"), 636 Package: proto.String("example"), 637 Dependency: []string{}, 638 MessageType: test.MsgDescs, 639 Service: []*descriptorpb.ServiceDescriptorProto{}, 640 Options: &descriptorpb.FileOptions{ 641 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 642 }, 643 }, 644 GoPkg: descriptor.GoPackage{ 645 Path: "example.com/path/to/example/example.pb", 646 Name: "example_pb", 647 }, 648 Messages: msgs, 649 } 650 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 651 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 652 }) 653 if err != nil { 654 t.Fatalf("failed to load code generator request: %v", err) 655 } 656 657 message, err := reg.LookupMsg("", ".example."+test.Message) 658 if err != nil { 659 t.Fatalf("failed to lookup message: %s", err) 660 } 661 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 662 if err != nil { 663 t.Fatalf("failed to convert message to query parameters: %s", err) 664 } 665 // avoid checking Items for array types 666 for i := range params { 667 params[i].Items = nil 668 } 669 if !reflect.DeepEqual(params, test.Params) { 670 t.Errorf("expected %v, got %v", test.Params, params) 671 } 672 } 673 } 674 675 // TestMessagetoQueryParametersNoRecursive, is a check that cyclical references between messages 676 // are not falsely detected given previous known edge-cases. 677 func TestMessageToQueryParametersNoRecursive(t *testing.T) { 678 type test struct { 679 MsgDescs []*descriptorpb.DescriptorProto 680 Message string 681 } 682 683 tests := []test{ 684 // First test: 685 // Here is a message that has two of another message adjacent to one another in a nested message. 686 // There is no loop but this was previouly falsely flagged as a cycle. 687 // Example proto: 688 // message NonRecursiveMessage { 689 // string field = 1; 690 // } 691 // message BaseMessage { 692 // NonRecursiveMessage first = 1; 693 // NonRecursiveMessage second = 2; 694 // } 695 // message QueryMessage { 696 // BaseMessage first = 1; 697 // string second = 2; 698 // } 699 { 700 MsgDescs: []*descriptorpb.DescriptorProto{ 701 { 702 Name: proto.String("QueryMessage"), 703 Field: []*descriptorpb.FieldDescriptorProto{ 704 { 705 Name: proto.String("first"), 706 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 707 TypeName: proto.String(".example.BaseMessage"), 708 Number: proto.Int32(1), 709 }, 710 { 711 Name: proto.String("second"), 712 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 713 Number: proto.Int32(2), 714 }, 715 }, 716 }, 717 { 718 Name: proto.String("BaseMessage"), 719 Field: []*descriptorpb.FieldDescriptorProto{ 720 { 721 Name: proto.String("first"), 722 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 723 TypeName: proto.String(".example.NonRecursiveMessage"), 724 Number: proto.Int32(1), 725 }, 726 { 727 Name: proto.String("second"), 728 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 729 TypeName: proto.String(".example.NonRecursiveMessage"), 730 Number: proto.Int32(2), 731 }, 732 }, 733 }, 734 // Note there is no recursive nature to this message 735 { 736 Name: proto.String("NonRecursiveMessage"), 737 Field: []*descriptorpb.FieldDescriptorProto{ 738 { 739 Name: proto.String("field"), 740 // Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 741 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 742 Number: proto.Int32(1), 743 }, 744 }, 745 }, 746 }, 747 Message: "QueryMessage", 748 }, 749 } 750 751 for _, test := range tests { 752 reg := descriptor.NewRegistry() 753 msgs := []*descriptor.Message{} 754 for _, msgdesc := range test.MsgDescs { 755 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 756 } 757 file := descriptor.File{ 758 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 759 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 760 Name: proto.String("example.proto"), 761 Package: proto.String("example"), 762 Dependency: []string{}, 763 MessageType: test.MsgDescs, 764 Service: []*descriptorpb.ServiceDescriptorProto{}, 765 Options: &descriptorpb.FileOptions{ 766 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 767 }, 768 }, 769 GoPkg: descriptor.GoPackage{ 770 Path: "example.com/path/to/example/example.pb", 771 Name: "example_pb", 772 }, 773 Messages: msgs, 774 } 775 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 776 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 777 }) 778 if err != nil { 779 t.Fatalf("failed to load code generator request: %v", err) 780 } 781 782 message, err := reg.LookupMsg("", ".example."+test.Message) 783 if err != nil { 784 t.Fatalf("failed to lookup message: %s", err) 785 } 786 787 _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 788 if err != nil { 789 t.Fatalf("No recursion error should be thrown: %s", err) 790 } 791 } 792 } 793 794 // TestMessagetoQueryParametersRecursive, is a check that cyclical references between messages 795 // are handled gracefully. The goal is to insure that attempts to add messages with cyclical 796 // references to query-parameters returns an error message. 797 func TestMessageToQueryParametersRecursive(t *testing.T) { 798 type test struct { 799 MsgDescs []*descriptorpb.DescriptorProto 800 Message string 801 } 802 803 tests := []test{ 804 // First test: 805 // Here we test that a message that references it self through a field will return an error. 806 // Example proto: 807 // message DirectRecursiveMessage { 808 // DirectRecursiveMessage nested = 1; 809 // } 810 { 811 MsgDescs: []*descriptorpb.DescriptorProto{ 812 { 813 Name: proto.String("DirectRecursiveMessage"), 814 Field: []*descriptorpb.FieldDescriptorProto{ 815 { 816 Name: proto.String("nested"), 817 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 818 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 819 TypeName: proto.String(".example.DirectRecursiveMessage"), 820 Number: proto.Int32(1), 821 }, 822 }, 823 }, 824 }, 825 Message: "DirectRecursiveMessage", 826 }, 827 // Second test: 828 // Here we test that a cycle through multiple messages is detected and that an error is returned. 829 // Sample: 830 // message Root { NodeMessage nested = 1; } 831 // message NodeMessage { CycleMessage nested = 1; } 832 // message CycleMessage { Root nested = 1; } 833 { 834 MsgDescs: []*descriptorpb.DescriptorProto{ 835 { 836 Name: proto.String("RootMessage"), 837 Field: []*descriptorpb.FieldDescriptorProto{ 838 { 839 Name: proto.String("nested"), 840 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 841 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 842 TypeName: proto.String(".example.NodeMessage"), 843 Number: proto.Int32(1), 844 }, 845 }, 846 }, 847 { 848 Name: proto.String("NodeMessage"), 849 Field: []*descriptorpb.FieldDescriptorProto{ 850 { 851 Name: proto.String("nested"), 852 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 853 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 854 TypeName: proto.String(".example.CycleMessage"), 855 Number: proto.Int32(1), 856 }, 857 }, 858 }, 859 { 860 Name: proto.String("CycleMessage"), 861 Field: []*descriptorpb.FieldDescriptorProto{ 862 { 863 Name: proto.String("nested"), 864 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 865 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 866 TypeName: proto.String(".example.RootMessage"), 867 Number: proto.Int32(1), 868 }, 869 }, 870 }, 871 }, 872 Message: "RootMessage", 873 }, 874 } 875 876 for _, test := range tests { 877 reg := descriptor.NewRegistry() 878 msgs := []*descriptor.Message{} 879 for _, msgdesc := range test.MsgDescs { 880 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 881 } 882 file := descriptor.File{ 883 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 884 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 885 Name: proto.String("example.proto"), 886 Package: proto.String("example"), 887 Dependency: []string{}, 888 MessageType: test.MsgDescs, 889 Service: []*descriptorpb.ServiceDescriptorProto{}, 890 Options: &descriptorpb.FileOptions{ 891 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 892 }, 893 }, 894 GoPkg: descriptor.GoPackage{ 895 Path: "example.com/path/to/example/example.pb", 896 Name: "example_pb", 897 }, 898 Messages: msgs, 899 } 900 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 901 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 902 }) 903 if err != nil { 904 t.Fatalf("failed to load code generator request: %v", err) 905 } 906 907 message, err := reg.LookupMsg("", ".example."+test.Message) 908 if err != nil { 909 t.Fatalf("failed to lookup message: %s", err) 910 } 911 _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 912 if err == nil { 913 t.Fatalf("It should not be allowed to have recursive query parameters") 914 } 915 } 916 } 917 918 func TestMessageToQueryParametersWithJsonName(t *testing.T) { 919 type test struct { 920 MsgDescs []*descriptorpb.DescriptorProto 921 Message string 922 Params []openapiParameterObject 923 } 924 925 var requiredField = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED} 926 var requiredFieldOptions = new(descriptorpb.FieldOptions) 927 proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField) 928 929 messageSchema := &openapi_options.Schema{ 930 JsonSchema: &openapi_options.JSONSchema{ 931 Required: []string{"test_field_b"}, 932 }, 933 } 934 messageOption := &descriptorpb.MessageOptions{} 935 proto.SetExtension(messageOption, openapi_options.E_Openapiv2Schema, messageSchema) 936 937 tests := []test{ 938 { 939 MsgDescs: []*descriptorpb.DescriptorProto{ 940 { 941 Name: proto.String("ExampleMessage"), 942 Field: []*descriptorpb.FieldDescriptorProto{ 943 { 944 Name: proto.String("test_field_a"), 945 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 946 Number: proto.Int32(1), 947 JsonName: proto.String("testFieldA"), 948 }, 949 }, 950 }, 951 }, 952 Message: "ExampleMessage", 953 Params: []openapiParameterObject{ 954 { 955 Name: "testFieldA", 956 In: "query", 957 Required: false, 958 Type: "string", 959 }, 960 }, 961 }, 962 { 963 MsgDescs: []*descriptorpb.DescriptorProto{ 964 { 965 Name: proto.String("SubMessage"), 966 Field: []*descriptorpb.FieldDescriptorProto{ 967 { 968 Name: proto.String("test_field_a"), 969 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 970 Number: proto.Int32(1), 971 JsonName: proto.String("testFieldA"), 972 }, 973 }, 974 }, 975 { 976 Name: proto.String("ExampleMessage"), 977 Field: []*descriptorpb.FieldDescriptorProto{ 978 { 979 Name: proto.String("sub_message"), 980 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 981 TypeName: proto.String(".example.SubMessage"), 982 Number: proto.Int32(1), 983 JsonName: proto.String("subMessage"), 984 }, 985 }, 986 }, 987 }, 988 Message: "ExampleMessage", 989 Params: []openapiParameterObject{ 990 { 991 Name: "subMessage.testFieldA", 992 In: "query", 993 Required: false, 994 Type: "string", 995 }, 996 }, 997 }, 998 { 999 MsgDescs: []*descriptorpb.DescriptorProto{ 1000 { 1001 Name: proto.String("ExampleMessage"), 1002 Field: []*descriptorpb.FieldDescriptorProto{ 1003 { 1004 Name: proto.String("test_field_a"), 1005 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1006 Number: proto.Int32(1), 1007 JsonName: proto.String("testFieldACustom"), 1008 Options: requiredFieldOptions, 1009 }, 1010 { 1011 Name: proto.String("test_field_b"), 1012 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1013 Number: proto.Int32(2), 1014 JsonName: proto.String("testFieldBCustom"), 1015 }, 1016 }, 1017 Options: messageOption, 1018 }, 1019 }, 1020 Message: "ExampleMessage", 1021 Params: []openapiParameterObject{ 1022 { 1023 Name: "testFieldACustom", 1024 In: "query", 1025 Required: true, 1026 Type: "string", 1027 }, 1028 { 1029 Name: "testFieldBCustom", 1030 In: "query", 1031 Required: true, 1032 Type: "string", 1033 }, 1034 }, 1035 }, 1036 } 1037 1038 for _, test := range tests { 1039 reg := descriptor.NewRegistry() 1040 reg.SetUseJSONNamesForFields(true) 1041 msgs := []*descriptor.Message{} 1042 for _, msgdesc := range test.MsgDescs { 1043 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 1044 } 1045 file := descriptor.File{ 1046 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1047 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1048 Name: proto.String("example.proto"), 1049 Package: proto.String("example"), 1050 Dependency: []string{}, 1051 MessageType: test.MsgDescs, 1052 Service: []*descriptorpb.ServiceDescriptorProto{}, 1053 Options: &descriptorpb.FileOptions{ 1054 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1055 }, 1056 }, 1057 GoPkg: descriptor.GoPackage{ 1058 Path: "example.com/path/to/example/example.pb", 1059 Name: "example_pb", 1060 }, 1061 Messages: msgs, 1062 } 1063 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 1064 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 1065 }) 1066 if err != nil { 1067 t.Fatalf("failed to load code generator request: %v", err) 1068 } 1069 1070 message, err := reg.LookupMsg("", ".example."+test.Message) 1071 if err != nil { 1072 t.Fatalf("failed to lookup message: %s", err) 1073 } 1074 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 1075 if err != nil { 1076 t.Fatalf("failed to convert message to query parameters: %s", err) 1077 } 1078 if !reflect.DeepEqual(params, test.Params) { 1079 t.Errorf("expected %#v, got %#v", test.Params, params) 1080 } 1081 } 1082 } 1083 1084 func TestMessageToQueryParametersWellKnownTypes(t *testing.T) { 1085 type test struct { 1086 MsgDescs []*descriptorpb.DescriptorProto 1087 WellKnownMsgDescs []*descriptorpb.DescriptorProto 1088 Message string 1089 Params []openapiParameterObject 1090 } 1091 1092 tests := []test{ 1093 { 1094 MsgDescs: []*descriptorpb.DescriptorProto{ 1095 { 1096 Name: proto.String("ExampleMessage"), 1097 Field: []*descriptorpb.FieldDescriptorProto{ 1098 { 1099 Name: proto.String("a_field_mask"), 1100 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 1101 TypeName: proto.String(".google.protobuf.FieldMask"), 1102 Number: proto.Int32(1), 1103 }, 1104 { 1105 Name: proto.String("a_timestamp"), 1106 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 1107 TypeName: proto.String(".google.protobuf.Timestamp"), 1108 Number: proto.Int32(2), 1109 }, 1110 }, 1111 }, 1112 }, 1113 WellKnownMsgDescs: []*descriptorpb.DescriptorProto{ 1114 { 1115 Name: proto.String("FieldMask"), 1116 Field: []*descriptorpb.FieldDescriptorProto{ 1117 { 1118 Name: proto.String("paths"), 1119 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1120 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 1121 Number: proto.Int32(1), 1122 }, 1123 }, 1124 }, 1125 { 1126 Name: proto.String("Timestamp"), 1127 Field: []*descriptorpb.FieldDescriptorProto{ 1128 { 1129 Name: proto.String("seconds"), 1130 Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(), 1131 Number: proto.Int32(1), 1132 }, 1133 { 1134 Name: proto.String("nanos"), 1135 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 1136 Number: proto.Int32(2), 1137 }, 1138 }, 1139 }, 1140 }, 1141 Message: "ExampleMessage", 1142 Params: []openapiParameterObject{ 1143 { 1144 Name: "a_field_mask", 1145 In: "query", 1146 Required: false, 1147 Type: "string", 1148 }, 1149 { 1150 Name: "a_timestamp", 1151 In: "query", 1152 Required: false, 1153 Type: "string", 1154 Format: "date-time", 1155 }, 1156 }, 1157 }, 1158 } 1159 1160 for _, test := range tests { 1161 reg := descriptor.NewRegistry() 1162 reg.SetEnumsAsInts(true) 1163 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 1164 ProtoFile: []*descriptorpb.FileDescriptorProto{ 1165 { 1166 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1167 Name: proto.String("google/well_known.proto"), 1168 Package: proto.String("google.protobuf"), 1169 Dependency: []string{}, 1170 MessageType: test.WellKnownMsgDescs, 1171 Service: []*descriptorpb.ServiceDescriptorProto{}, 1172 Options: &descriptorpb.FileOptions{ 1173 GoPackage: proto.String("google/well_known"), 1174 }, 1175 }, 1176 { 1177 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1178 Name: proto.String("acme/example.proto"), 1179 Package: proto.String("example"), 1180 Dependency: []string{"google/well_known.proto"}, 1181 MessageType: test.MsgDescs, 1182 Service: []*descriptorpb.ServiceDescriptorProto{}, 1183 Options: &descriptorpb.FileOptions{ 1184 GoPackage: proto.String("acme/example"), 1185 }, 1186 }, 1187 }, 1188 }) 1189 if err != nil { 1190 t.Fatalf("failed to load CodeGeneratorRequest: %v", err) 1191 } 1192 1193 message, err := reg.LookupMsg("", ".example."+test.Message) 1194 if err != nil { 1195 t.Fatalf("failed to lookup message: %s", err) 1196 } 1197 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 1198 if err != nil { 1199 t.Fatalf("failed to convert message to query parameters: %s", err) 1200 } 1201 if !reflect.DeepEqual(params, test.Params) { 1202 t.Errorf("expected %v, got %v", test.Params, params) 1203 } 1204 } 1205 } 1206 1207 func TestMessageToQueryParametersWithRequiredField(t *testing.T) { 1208 type test struct { 1209 MsgDescs []*descriptorpb.DescriptorProto 1210 Message string 1211 Params []openapiParameterObject 1212 } 1213 1214 messageSchema := &openapi_options.Schema{ 1215 JsonSchema: &openapi_options.JSONSchema{ 1216 Required: []string{"a"}, 1217 }, 1218 } 1219 messageOption := &descriptorpb.MessageOptions{} 1220 proto.SetExtension(messageOption, openapi_options.E_Openapiv2Schema, messageSchema) 1221 1222 fieldSchema := &openapi_options.JSONSchema{Required: []string{"b"}} 1223 fieldOption := &descriptorpb.FieldOptions{} 1224 proto.SetExtension(fieldOption, openapi_options.E_Openapiv2Field, fieldSchema) 1225 1226 // TODO(makdon): is nested field's test case necessary here? 1227 tests := []test{ 1228 { 1229 MsgDescs: []*descriptorpb.DescriptorProto{ 1230 { 1231 Name: proto.String("ExampleMessage"), 1232 Field: []*descriptorpb.FieldDescriptorProto{ 1233 { 1234 Name: proto.String("a"), 1235 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1236 Number: proto.Int32(1), 1237 }, 1238 { 1239 Name: proto.String("b"), 1240 Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(), 1241 Number: proto.Int32(2), 1242 Options: fieldOption, 1243 }, 1244 { 1245 Name: proto.String("c"), 1246 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1247 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 1248 Number: proto.Int32(3), 1249 }, 1250 }, 1251 Options: messageOption, 1252 }, 1253 }, 1254 Message: "ExampleMessage", 1255 Params: []openapiParameterObject{ 1256 { 1257 Name: "a", 1258 In: "query", 1259 Required: true, 1260 Type: "string", 1261 }, 1262 { 1263 Name: "b", 1264 In: "query", 1265 Required: true, 1266 Type: "number", 1267 Format: "double", 1268 }, 1269 { 1270 Name: "c", 1271 In: "query", 1272 Required: false, 1273 Type: "array", 1274 CollectionFormat: "multi", 1275 }, 1276 }, 1277 }, 1278 } 1279 1280 for _, test := range tests { 1281 reg := descriptor.NewRegistry() 1282 msgs := []*descriptor.Message{} 1283 for _, msgdesc := range test.MsgDescs { 1284 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 1285 } 1286 file := descriptor.File{ 1287 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1288 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1289 Name: proto.String("example.proto"), 1290 Package: proto.String("example"), 1291 Dependency: []string{}, 1292 MessageType: test.MsgDescs, 1293 Service: []*descriptorpb.ServiceDescriptorProto{}, 1294 Options: &descriptorpb.FileOptions{ 1295 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1296 }, 1297 }, 1298 GoPkg: descriptor.GoPackage{ 1299 Path: "example.com/path/to/example/example.pb", 1300 Name: "example_pb", 1301 }, 1302 Messages: msgs, 1303 } 1304 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 1305 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 1306 }) 1307 if err != nil { 1308 t.Fatalf("failed to load code generator request: %v", err) 1309 } 1310 1311 message, err := reg.LookupMsg("", ".example."+test.Message) 1312 if err != nil { 1313 t.Fatalf("failed to lookup message: %s", err) 1314 } 1315 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 1316 if err != nil { 1317 t.Fatalf("failed to convert message to query parameters: %s", err) 1318 } 1319 // avoid checking Items for array types 1320 for i := range params { 1321 params[i].Items = nil 1322 } 1323 if !reflect.DeepEqual(params, test.Params) { 1324 t.Errorf("expected %v, got %v", test.Params, params) 1325 } 1326 } 1327 } 1328 1329 func TestMessageToQueryParametersWithEnumFieldOption(t *testing.T) { 1330 type test struct { 1331 MsgDescs []*descriptorpb.DescriptorProto 1332 Message string 1333 Params []openapiParameterObject 1334 } 1335 1336 fieldSchema := &openapi_options.JSONSchema{Enum: []string{"enum1", "enum2"}} 1337 fieldOption := &descriptorpb.FieldOptions{} 1338 proto.SetExtension(fieldOption, openapi_options.E_Openapiv2Field, fieldSchema) 1339 1340 tests := []test{ 1341 { 1342 MsgDescs: []*descriptorpb.DescriptorProto{ 1343 { 1344 Name: proto.String("ExampleMessage"), 1345 Field: []*descriptorpb.FieldDescriptorProto{ 1346 { 1347 Name: proto.String("a"), 1348 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1349 Number: proto.Int32(1), 1350 Options: fieldOption, 1351 }, 1352 { 1353 Name: proto.String("b"), 1354 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 1355 Number: proto.Int32(2), 1356 }, 1357 { 1358 Name: proto.String("c"), 1359 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 1360 TypeName: proto.String(".example.ExampleMessage.EnabledEnum"), 1361 Number: proto.Int32(3), 1362 }, 1363 { 1364 Name: proto.String("d"), 1365 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 1366 TypeName: proto.String(".example.ExampleMessage.EnabledEnum"), 1367 Number: proto.Int32(4), 1368 Options: fieldOption, 1369 }, 1370 }, 1371 EnumType: []*descriptorpb.EnumDescriptorProto{ 1372 { 1373 Name: proto.String("EnabledEnum"), 1374 Value: []*descriptorpb.EnumValueDescriptorProto{ 1375 {Name: proto.String("FALSE"), Number: proto.Int32(0)}, 1376 {Name: proto.String("TRUE"), Number: proto.Int32(1)}, 1377 }, 1378 }, 1379 }, 1380 }, 1381 }, 1382 Message: "ExampleMessage", 1383 Params: []openapiParameterObject{ 1384 { 1385 Name: "a", 1386 In: "query", 1387 Type: "string", 1388 Enum: []string{"enum1", "enum2"}, 1389 }, 1390 { 1391 Name: "b", 1392 In: "query", 1393 Type: "string", 1394 }, 1395 { 1396 Name: "c", 1397 In: "query", 1398 Type: "string", 1399 Enum: []string{"FALSE", "TRUE"}, 1400 Default: "FALSE", 1401 }, 1402 { 1403 Name: "d", 1404 In: "query", 1405 Type: "string", 1406 Enum: []string{"FALSE", "TRUE"}, 1407 Default: "FALSE", 1408 }, 1409 }, 1410 }, 1411 } 1412 1413 for _, test := range tests { 1414 reg := descriptor.NewRegistry() 1415 msgs := []*descriptor.Message{} 1416 for _, msgdesc := range test.MsgDescs { 1417 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 1418 } 1419 file := descriptor.File{ 1420 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1421 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1422 Name: proto.String("example.proto"), 1423 Package: proto.String("example"), 1424 Dependency: []string{}, 1425 MessageType: test.MsgDescs, 1426 Service: []*descriptorpb.ServiceDescriptorProto{}, 1427 Options: &descriptorpb.FileOptions{ 1428 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1429 }, 1430 }, 1431 GoPkg: descriptor.GoPackage{ 1432 Path: "example.com/path/to/example/example.pb", 1433 Name: "example_pb", 1434 }, 1435 Messages: msgs, 1436 } 1437 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 1438 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 1439 }) 1440 if err != nil { 1441 t.Fatalf("failed to load code generator request: %v", err) 1442 } 1443 1444 message, err := reg.LookupMsg("", ".example."+test.Message) 1445 if err != nil { 1446 t.Fatalf("failed to lookup message: %s", err) 1447 } 1448 params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "") 1449 if err != nil { 1450 t.Fatalf("failed to convert message to query parameters: %s", err) 1451 } 1452 // avoid checking Items for array types 1453 for i := range params { 1454 params[i].Items = nil 1455 } 1456 if !reflect.DeepEqual(params, test.Params) { 1457 t.Errorf("expected %v, got %v", test.Params, params) 1458 } 1459 } 1460 } 1461 1462 func TestApplyTemplateSimple(t *testing.T) { 1463 msgdesc := &descriptorpb.DescriptorProto{ 1464 Name: proto.String("ExampleMessage"), 1465 } 1466 meth := &descriptorpb.MethodDescriptorProto{ 1467 Name: proto.String("Example"), 1468 InputType: proto.String("ExampleMessage"), 1469 OutputType: proto.String("ExampleMessage"), 1470 } 1471 svc := &descriptorpb.ServiceDescriptorProto{ 1472 Name: proto.String("ExampleService"), 1473 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1474 } 1475 msg := &descriptor.Message{ 1476 DescriptorProto: msgdesc, 1477 } 1478 file := descriptor.File{ 1479 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1480 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1481 Name: proto.String("example.proto"), 1482 Package: proto.String("example"), 1483 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 1484 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 1485 Options: &descriptorpb.FileOptions{ 1486 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1487 }, 1488 }, 1489 GoPkg: descriptor.GoPackage{ 1490 Path: "example.com/path/to/example/example.pb", 1491 Name: "example_pb", 1492 }, 1493 Messages: []*descriptor.Message{msg}, 1494 Services: []*descriptor.Service{ 1495 { 1496 ServiceDescriptorProto: svc, 1497 Methods: []*descriptor.Method{ 1498 { 1499 MethodDescriptorProto: meth, 1500 RequestType: msg, 1501 ResponseType: msg, 1502 Bindings: []*descriptor.Binding{ 1503 { 1504 HTTPMethod: "GET", 1505 Body: &descriptor.Body{FieldPath: nil}, 1506 PathTmpl: httprule.Template{ 1507 Version: 1, 1508 OpCodes: []int{0, 0}, 1509 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 1510 }, 1511 }, 1512 }, 1513 }, 1514 }, 1515 }, 1516 }, 1517 } 1518 reg := descriptor.NewRegistry() 1519 if err := AddErrorDefs(reg); err != nil { 1520 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 1521 return 1522 } 1523 fileCL := crossLinkFixture(&file) 1524 err := reg.Load(reqFromFile(fileCL)) 1525 if err != nil { 1526 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 1527 return 1528 } 1529 result, err := applyTemplate(param{File: fileCL, reg: reg}) 1530 if err != nil { 1531 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 1532 return 1533 } 1534 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) { 1535 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1536 } 1537 if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) { 1538 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1539 } 1540 if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) { 1541 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1542 } 1543 if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) { 1544 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1545 } 1546 if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) { 1547 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1548 } 1549 1550 // If there was a failure, print out the input and the json result for debugging. 1551 if t.Failed() { 1552 t.Errorf("had: %s", file) 1553 t.Errorf("got: %s", fmt.Sprint(result)) 1554 } 1555 } 1556 1557 func TestApplyTemplateMultiService(t *testing.T) { 1558 msgdesc := &descriptorpb.DescriptorProto{ 1559 Name: proto.String("ExampleMessage"), 1560 } 1561 meth := &descriptorpb.MethodDescriptorProto{ 1562 Name: proto.String("Example"), 1563 InputType: proto.String("ExampleMessage"), 1564 OutputType: proto.String("ExampleMessage"), 1565 } 1566 1567 // Create two services that have the same method name. We will test that the 1568 // operation IDs are different 1569 svc := &descriptorpb.ServiceDescriptorProto{ 1570 Name: proto.String("ExampleService"), 1571 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1572 } 1573 svc2 := &descriptorpb.ServiceDescriptorProto{ 1574 Name: proto.String("OtherService"), 1575 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1576 } 1577 1578 msg := &descriptor.Message{ 1579 DescriptorProto: msgdesc, 1580 } 1581 file := descriptor.File{ 1582 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1583 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1584 Name: proto.String("example.proto"), 1585 Package: proto.String("example"), 1586 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 1587 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 1588 Options: &descriptorpb.FileOptions{ 1589 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1590 }, 1591 }, 1592 GoPkg: descriptor.GoPackage{ 1593 Path: "example.com/path/to/example/example.pb", 1594 Name: "example_pb", 1595 }, 1596 Messages: []*descriptor.Message{msg}, 1597 Services: []*descriptor.Service{ 1598 { 1599 ServiceDescriptorProto: svc, 1600 Methods: []*descriptor.Method{ 1601 { 1602 MethodDescriptorProto: meth, 1603 RequestType: msg, 1604 ResponseType: msg, 1605 Bindings: []*descriptor.Binding{ 1606 { 1607 HTTPMethod: "GET", 1608 Body: &descriptor.Body{FieldPath: nil}, 1609 PathTmpl: httprule.Template{ 1610 Version: 1, 1611 OpCodes: []int{0, 0}, 1612 Template: "/v1/echo", 1613 }, 1614 }, 1615 }, 1616 }, 1617 }, 1618 }, 1619 { 1620 ServiceDescriptorProto: svc2, 1621 Methods: []*descriptor.Method{ 1622 { 1623 MethodDescriptorProto: meth, 1624 RequestType: msg, 1625 ResponseType: msg, 1626 Bindings: []*descriptor.Binding{ 1627 { 1628 HTTPMethod: "GET", 1629 Body: &descriptor.Body{FieldPath: nil}, 1630 PathTmpl: httprule.Template{ 1631 Version: 1, 1632 OpCodes: []int{0, 0}, 1633 Template: "/v1/ping", 1634 }, 1635 }, 1636 }, 1637 }, 1638 }, 1639 }, 1640 }, 1641 } 1642 reg := descriptor.NewRegistry() 1643 if err := AddErrorDefs(reg); err != nil { 1644 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 1645 return 1646 } 1647 fileCL := crossLinkFixture(&file) 1648 err := reg.Load(reqFromFile(fileCL)) 1649 if err != nil { 1650 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 1651 return 1652 } 1653 result, err := applyTemplate(param{File: fileCL, reg: reg}) 1654 if err != nil { 1655 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 1656 return 1657 } 1658 1659 // Check that the two services have unique operation IDs even though they 1660 // have the same method name. 1661 if want, is := "ExampleService_Example", result.getPathItemObject("/v1/echo").Get.OperationID; !reflect.DeepEqual(is, want) { 1662 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want) 1663 } 1664 if want, is := "OtherService_Example", result.getPathItemObject("/v1/ping").Get.OperationID; !reflect.DeepEqual(is, want) { 1665 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want) 1666 } 1667 1668 // If there was a failure, print out the input and the json result for debugging. 1669 if t.Failed() { 1670 t.Errorf("had: %s", file) 1671 t.Errorf("got: %s", fmt.Sprint(result)) 1672 } 1673 } 1674 1675 func TestApplyTemplateOpenAPIConfigFromYAML(t *testing.T) { 1676 msgdesc := &descriptorpb.DescriptorProto{ 1677 Name: proto.String("ExampleMessage"), 1678 } 1679 meth := &descriptorpb.MethodDescriptorProto{ 1680 Name: proto.String("Example"), 1681 InputType: proto.String("ExampleMessage"), 1682 OutputType: proto.String("ExampleMessage"), 1683 } 1684 svc := &descriptorpb.ServiceDescriptorProto{ 1685 Name: proto.String("ExampleService"), 1686 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1687 } 1688 msg := &descriptor.Message{ 1689 DescriptorProto: msgdesc, 1690 } 1691 file := descriptor.File{ 1692 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1693 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1694 Name: proto.String("example.proto"), 1695 Package: proto.String("example"), 1696 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 1697 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 1698 Options: &descriptorpb.FileOptions{ 1699 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1700 }, 1701 }, 1702 GoPkg: descriptor.GoPackage{ 1703 Path: "example.com/path/to/example/example.pb", 1704 Name: "example_pb", 1705 }, 1706 Messages: []*descriptor.Message{msg}, 1707 Services: []*descriptor.Service{ 1708 { 1709 ServiceDescriptorProto: svc, 1710 Methods: []*descriptor.Method{ 1711 { 1712 MethodDescriptorProto: meth, 1713 RequestType: msg, 1714 ResponseType: msg, 1715 Bindings: []*descriptor.Binding{ 1716 { 1717 HTTPMethod: "GET", 1718 Body: &descriptor.Body{FieldPath: nil}, 1719 PathTmpl: httprule.Template{ 1720 Version: 1, 1721 OpCodes: []int{0, 0}, 1722 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 1723 }, 1724 }, 1725 }, 1726 }, 1727 }, 1728 }, 1729 }, 1730 } 1731 reg := descriptor.NewRegistry() 1732 if err := AddErrorDefs(reg); err != nil { 1733 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 1734 return 1735 } 1736 fileCL := crossLinkFixture(&file) 1737 err := reg.Load(reqFromFile(fileCL)) 1738 if err != nil { 1739 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 1740 return 1741 } 1742 openapiOptions := &openapiconfig.OpenAPIOptions{ 1743 Service: []*openapiconfig.OpenAPIServiceOption{ 1744 { 1745 Service: "example.ExampleService", 1746 Option: &openapi_options.Tag{ 1747 Description: "ExampleService description", 1748 ExternalDocs: &openapi_options.ExternalDocumentation{ 1749 Description: "Find out more about ExampleService", 1750 }, 1751 }, 1752 }, 1753 }, 1754 } 1755 if err := reg.RegisterOpenAPIOptions(openapiOptions); err != nil { 1756 t.Errorf("reg.RegisterOpenAPIOptions for Service %#v failed with %v; want success", openapiOptions.Service, err) 1757 return 1758 } 1759 1760 result, err := applyTemplate(param{File: fileCL, reg: reg}) 1761 if err != nil { 1762 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 1763 return 1764 } 1765 if want, is, name := "ExampleService description", result.Tags[0].Description, "Tags[0].Description"; !reflect.DeepEqual(is, want) { 1766 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1767 } 1768 if want, is, name := "Find out more about ExampleService", result.Tags[0].ExternalDocs.Description, "Tags[0].ExternalDocs.Description"; !reflect.DeepEqual(is, want) { 1769 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 1770 } 1771 1772 reg.SetDisableServiceTags(true) 1773 1774 res, err := applyTemplate(param{File: fileCL, reg: reg}) 1775 if err != nil { 1776 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 1777 return 1778 } 1779 1780 if got, want := len(res.Tags), 0; got != want { 1781 t.Fatalf("len(applyTemplate(%#v).Tags) = %d want to be %d", file, got, want) 1782 } 1783 1784 // If there was a failure, print out the input and the json result for debugging. 1785 if t.Failed() { 1786 t.Errorf("had: %s", file) 1787 t.Errorf("got: %s", fmt.Sprint(result)) 1788 } 1789 } 1790 1791 func TestApplyTemplateOverrideWithOperation(t *testing.T) { 1792 newFile := func() *descriptor.File { 1793 msgdesc := &descriptorpb.DescriptorProto{ 1794 Name: proto.String("ExampleMessage"), 1795 } 1796 meth := &descriptorpb.MethodDescriptorProto{ 1797 Name: proto.String("Example"), 1798 InputType: proto.String("ExampleMessage"), 1799 OutputType: proto.String("ExampleMessage"), 1800 Options: &descriptorpb.MethodOptions{}, 1801 } 1802 svc := &descriptorpb.ServiceDescriptorProto{ 1803 Name: proto.String("ExampleService"), 1804 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1805 } 1806 msg := &descriptor.Message{ 1807 DescriptorProto: msgdesc, 1808 } 1809 return &descriptor.File{ 1810 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1811 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1812 Name: proto.String("example.proto"), 1813 Package: proto.String("example"), 1814 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 1815 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 1816 Options: &descriptorpb.FileOptions{ 1817 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1818 }, 1819 }, 1820 GoPkg: descriptor.GoPackage{ 1821 Path: "example.com/path/to/example/example.pb", 1822 Name: "example_pb", 1823 }, 1824 Messages: []*descriptor.Message{msg}, 1825 Services: []*descriptor.Service{ 1826 { 1827 ServiceDescriptorProto: svc, 1828 Methods: []*descriptor.Method{ 1829 { 1830 MethodDescriptorProto: meth, 1831 RequestType: msg, 1832 ResponseType: msg, 1833 Bindings: []*descriptor.Binding{ 1834 { 1835 HTTPMethod: "GET", 1836 Body: &descriptor.Body{FieldPath: nil}, 1837 PathTmpl: httprule.Template{ 1838 Version: 1, 1839 OpCodes: []int{0, 0}, 1840 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 1841 }, 1842 }, 1843 }, 1844 }, 1845 }, 1846 }, 1847 }, 1848 } 1849 } 1850 1851 verifyTemplateFromReq := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File, opts *openapiconfig.OpenAPIOptions) { 1852 if err := AddErrorDefs(reg); err != nil { 1853 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 1854 return 1855 } 1856 fileCL := crossLinkFixture(file) 1857 err := reg.Load(reqFromFile(fileCL)) 1858 if err != nil { 1859 t.Errorf("reg.Load(%#v) failed with %v; want success", *file, err) 1860 return 1861 } 1862 if opts != nil { 1863 if err := reg.RegisterOpenAPIOptions(opts); err != nil { 1864 t.Fatalf("failed to register OpenAPI options: %s", err) 1865 } 1866 } 1867 result, err := applyTemplate(param{File: fileCL, reg: reg}) 1868 if err != nil { 1869 t.Errorf("applyTemplate(%#v) failed with %v; want success", *file, err) 1870 return 1871 } 1872 1873 if want, is := "MyExample", result.getPathItemObject("/v1/echo").Get.OperationID; !reflect.DeepEqual(is, want) { 1874 t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", *file, is, want) 1875 } 1876 if want, is := []string{"application/xml"}, result.getPathItemObject("/v1/echo").Get.Consumes; !reflect.DeepEqual(is, want) { 1877 t.Errorf("applyTemplate(%#v).Paths[0].Get.Consumes = %s want to be %s", *file, is, want) 1878 } 1879 if want, is := []string{"application/json", "application/xml"}, result.getPathItemObject("/v1/echo").Get.Produces; !reflect.DeepEqual(is, want) { 1880 t.Errorf("applyTemplate(%#v).Paths[0].Get.Produces = %s want to be %s", *file, is, want) 1881 } 1882 1883 // If there was a failure, print out the input and the json result for debugging. 1884 if t.Failed() { 1885 t.Errorf("had: %s", *file) 1886 t.Errorf("got: %s", fmt.Sprint(result)) 1887 } 1888 } 1889 1890 openapiOperation := openapi_options.Operation{ 1891 OperationId: "MyExample", 1892 Consumes: []string{"application/xml"}, 1893 Produces: []string{"application/json", "application/xml"}, 1894 } 1895 1896 t.Run("verify override via method option", func(t *testing.T) { 1897 file := newFile() 1898 proto.SetExtension(proto.Message(file.Services[0].Methods[0].MethodDescriptorProto.Options), 1899 openapi_options.E_Openapiv2Operation, &openapiOperation) 1900 1901 reg := descriptor.NewRegistry() 1902 verifyTemplateFromReq(t, reg, file, nil) 1903 }) 1904 1905 t.Run("verify override options annotations", func(t *testing.T) { 1906 file := newFile() 1907 reg := descriptor.NewRegistry() 1908 opts := &openapiconfig.OpenAPIOptions{ 1909 Method: []*openapiconfig.OpenAPIMethodOption{ 1910 { 1911 Method: "example.ExampleService.Example", 1912 Option: &openapiOperation, 1913 }, 1914 }, 1915 } 1916 verifyTemplateFromReq(t, reg, file, opts) 1917 }) 1918 } 1919 1920 func TestApplyTemplateExtensions(t *testing.T) { 1921 newFile := func() *descriptor.File { 1922 msgdesc := &descriptorpb.DescriptorProto{ 1923 Name: proto.String("ExampleMessage"), 1924 } 1925 meth := &descriptorpb.MethodDescriptorProto{ 1926 Name: proto.String("Example"), 1927 InputType: proto.String("ExampleMessage"), 1928 OutputType: proto.String("ExampleMessage"), 1929 Options: &descriptorpb.MethodOptions{}, 1930 } 1931 svc := &descriptorpb.ServiceDescriptorProto{ 1932 Name: proto.String("ExampleService"), 1933 Method: []*descriptorpb.MethodDescriptorProto{meth}, 1934 } 1935 msg := &descriptor.Message{ 1936 DescriptorProto: msgdesc, 1937 } 1938 return &descriptor.File{ 1939 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 1940 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 1941 Name: proto.String("example.proto"), 1942 Package: proto.String("example"), 1943 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 1944 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 1945 Options: &descriptorpb.FileOptions{ 1946 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 1947 }, 1948 }, 1949 GoPkg: descriptor.GoPackage{ 1950 Path: "example.com/path/to/example/example.pb", 1951 Name: "example_pb", 1952 }, 1953 Messages: []*descriptor.Message{msg}, 1954 Services: []*descriptor.Service{ 1955 { 1956 ServiceDescriptorProto: svc, 1957 Methods: []*descriptor.Method{ 1958 { 1959 MethodDescriptorProto: meth, 1960 RequestType: msg, 1961 ResponseType: msg, 1962 Bindings: []*descriptor.Binding{ 1963 { 1964 HTTPMethod: "GET", 1965 Body: &descriptor.Body{FieldPath: nil}, 1966 PathTmpl: httprule.Template{ 1967 Version: 1, 1968 OpCodes: []int{0, 0}, 1969 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 1970 }, 1971 }, 1972 }, 1973 }, 1974 }, 1975 }, 1976 }, 1977 } 1978 } 1979 swagger := openapi_options.Swagger{ 1980 Info: &openapi_options.Info{ 1981 Title: "test", 1982 Extensions: map[string]*structpb.Value{ 1983 "x-info-extension": {Kind: &structpb.Value_StringValue{StringValue: "bar"}}, 1984 }, 1985 }, 1986 Extensions: map[string]*structpb.Value{ 1987 "x-foo": {Kind: &structpb.Value_StringValue{StringValue: "bar"}}, 1988 "x-bar": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{ 1989 Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "baz"}}}, 1990 }}}, 1991 }, 1992 SecurityDefinitions: &openapi_options.SecurityDefinitions{ 1993 Security: map[string]*openapi_options.SecurityScheme{ 1994 "somescheme": { 1995 Extensions: map[string]*structpb.Value{ 1996 "x-security-baz": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, 1997 }, 1998 }, 1999 }, 2000 }, 2001 Tags: []*openapi_options.Tag{ 2002 { 2003 Name: "test tag", 2004 Description: "test tag description", 2005 Extensions: map[string]*structpb.Value{ 2006 "x-traitTag": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, 2007 }, 2008 }, 2009 }, 2010 } 2011 openapiOperation := openapi_options.Operation{ 2012 Responses: map[string]*openapi_options.Response{ 2013 "200": { 2014 Extensions: map[string]*structpb.Value{ 2015 "x-resp-id": {Kind: &structpb.Value_StringValue{StringValue: "resp1000"}}, 2016 }, 2017 }, 2018 }, 2019 Extensions: map[string]*structpb.Value{ 2020 "x-op-foo": {Kind: &structpb.Value_StringValue{StringValue: "baz"}}, 2021 }, 2022 } 2023 verifyTemplateExtensions := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File, 2024 opts *openapiconfig.OpenAPIOptions) { 2025 if err := AddErrorDefs(reg); err != nil { 2026 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 2027 return 2028 } 2029 fileCL := crossLinkFixture(file) 2030 err := reg.Load(reqFromFile(fileCL)) 2031 if err != nil { 2032 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 2033 return 2034 } 2035 if opts != nil { 2036 if err := reg.RegisterOpenAPIOptions(opts); err != nil { 2037 t.Fatalf("failed to register OpenAPI annotations: %s", err) 2038 } 2039 } 2040 result, err := applyTemplate(param{File: fileCL, reg: reg}) 2041 if err != nil { 2042 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 2043 return 2044 } 2045 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) { 2046 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2047 } 2048 if got, want := len(result.extensions), 2; got != want { 2049 t.Fatalf("len(applyTemplate(%#v).Extensions) = %d want to be %d", file, got, want) 2050 } 2051 if got, want := result.extensions[0].key, "x-bar"; got != want { 2052 t.Errorf("applyTemplate(%#v).Extensions[0].key = %s want to be %s", file, got, want) 2053 } 2054 if got, want := result.extensions[1].key, "x-foo"; got != want { 2055 t.Errorf("applyTemplate(%#v).Extensions[1].key = %s want to be %s", file, got, want) 2056 } 2057 { 2058 var got []string 2059 err = marshaler.Unmarshal(result.extensions[0].value, &got) 2060 if err != nil { 2061 t.Fatalf("marshaler.Unmarshal failed: %v", err) 2062 } 2063 want := []string{"baz"} 2064 if diff := cmp.Diff(got, want); diff != "" { 2065 t.Errorf(diff) 2066 } 2067 } 2068 { 2069 var got string 2070 err = marshaler.Unmarshal(result.extensions[1].value, &got) 2071 if err != nil { 2072 t.Fatalf("marshaler.Unmarshal failed: %v", err) 2073 } 2074 want := "bar" 2075 if diff := cmp.Diff(got, want); diff != "" { 2076 t.Errorf(diff) 2077 } 2078 } 2079 2080 var scheme openapiSecuritySchemeObject 2081 for _, v := range result.SecurityDefinitions { 2082 scheme = v 2083 } 2084 if want, is, name := []extension{ 2085 {key: "x-security-baz", value: json.RawMessage("true")}, 2086 }, scheme.extensions, "SecurityScheme.Extensions"; !reflect.DeepEqual(is, want) { 2087 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2088 } 2089 2090 if want, is, name := []extension{ 2091 {key: "x-info-extension", value: json.RawMessage("\"bar\"")}, 2092 }, result.Info.extensions, "Info.Extensions"; !reflect.DeepEqual(is, want) { 2093 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2094 } 2095 2096 var operation *openapiOperationObject 2097 var response openapiResponseObject 2098 for _, v := range result.Paths { 2099 operation = v.PathItemObject.Get 2100 response = v.PathItemObject.Get.Responses["200"] 2101 } 2102 if want, is, name := []extension{ 2103 {key: "x-op-foo", value: json.RawMessage("\"baz\"")}, 2104 }, operation.extensions, "operation.Extensions"; !reflect.DeepEqual(is, want) { 2105 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2106 } 2107 if want, is, name := []extension{ 2108 {key: "x-resp-id", value: json.RawMessage("\"resp1000\"")}, 2109 }, response.extensions, "response.Extensions"; !reflect.DeepEqual(is, want) { 2110 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2111 } 2112 2113 if len(result.Tags) == 0 { 2114 t.Errorf("No tags found in result") 2115 return 2116 } 2117 tag := result.Tags[0] 2118 if want, is, name := []extension{ 2119 {key: "x-traitTag", value: json.RawMessage("true")}, 2120 }, tag.extensions, "Tags[0].Extensions"; !reflect.DeepEqual(is, want) { 2121 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2122 } 2123 } 2124 t.Run("verify template options set via proto options", func(t *testing.T) { 2125 file := newFile() 2126 proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger) 2127 proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation) 2128 reg := descriptor.NewRegistry() 2129 verifyTemplateExtensions(t, reg, file, nil) 2130 }) 2131 t.Run("verify template options set via annotations", func(t *testing.T) { 2132 file := newFile() 2133 opts := &openapiconfig.OpenAPIOptions{ 2134 File: []*openapiconfig.OpenAPIFileOption{ 2135 { 2136 File: "example.proto", 2137 Option: &swagger, 2138 }, 2139 }, 2140 Method: []*openapiconfig.OpenAPIMethodOption{ 2141 { 2142 Method: "example.ExampleService.Example", 2143 Option: &openapiOperation, 2144 }, 2145 }, 2146 } 2147 reg := descriptor.NewRegistry() 2148 verifyTemplateExtensions(t, reg, file, opts) 2149 }) 2150 } 2151 2152 func TestApplyTemplateHeaders(t *testing.T) { 2153 newFile := func() *descriptor.File { 2154 msgdesc := &descriptorpb.DescriptorProto{ 2155 Name: proto.String("ExampleMessage"), 2156 } 2157 meth := &descriptorpb.MethodDescriptorProto{ 2158 Name: proto.String("Example"), 2159 InputType: proto.String("ExampleMessage"), 2160 OutputType: proto.String("ExampleMessage"), 2161 Options: &descriptorpb.MethodOptions{}, 2162 } 2163 svc := &descriptorpb.ServiceDescriptorProto{ 2164 Name: proto.String("ExampleService"), 2165 Method: []*descriptorpb.MethodDescriptorProto{meth}, 2166 } 2167 msg := &descriptor.Message{ 2168 DescriptorProto: msgdesc, 2169 } 2170 return &descriptor.File{ 2171 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 2172 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 2173 Name: proto.String("example.proto"), 2174 Package: proto.String("example"), 2175 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 2176 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 2177 Options: &descriptorpb.FileOptions{ 2178 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 2179 }, 2180 }, 2181 GoPkg: descriptor.GoPackage{ 2182 Path: "example.com/path/to/example/example.pb", 2183 Name: "example_pb", 2184 }, 2185 Messages: []*descriptor.Message{msg}, 2186 Services: []*descriptor.Service{ 2187 { 2188 ServiceDescriptorProto: svc, 2189 Methods: []*descriptor.Method{ 2190 { 2191 MethodDescriptorProto: meth, 2192 RequestType: msg, 2193 ResponseType: msg, 2194 Bindings: []*descriptor.Binding{ 2195 { 2196 HTTPMethod: "GET", 2197 Body: &descriptor.Body{FieldPath: nil}, 2198 PathTmpl: httprule.Template{ 2199 Version: 1, 2200 OpCodes: []int{0, 0}, 2201 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 2202 }, 2203 }, 2204 }, 2205 }, 2206 }, 2207 }, 2208 }, 2209 } 2210 } 2211 openapiOperation := openapi_options.Operation{ 2212 Responses: map[string]*openapi_options.Response{ 2213 "200": { 2214 Description: "Testing Headers", 2215 Headers: map[string]*openapi_options.Header{ 2216 "string": { 2217 Description: "string header description", 2218 Type: "string", 2219 Format: "uuid", 2220 Pattern: "", 2221 }, 2222 "boolean": { 2223 Description: "boolean header description", 2224 Type: "boolean", 2225 Default: "true", 2226 Pattern: "^true|false$", 2227 }, 2228 "integer": { 2229 Description: "integer header description", 2230 Type: "integer", 2231 Default: "0", 2232 Pattern: "^[0-9]$", 2233 }, 2234 "number": { 2235 Description: "number header description", 2236 Type: "number", 2237 Default: "1.2", 2238 Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$", 2239 }, 2240 }, 2241 }, 2242 }, 2243 } 2244 verifyTemplateHeaders := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File, 2245 opts *openapiconfig.OpenAPIOptions) { 2246 if err := AddErrorDefs(reg); err != nil { 2247 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 2248 return 2249 } 2250 fileCL := crossLinkFixture(file) 2251 err := reg.Load(reqFromFile(fileCL)) 2252 if err != nil { 2253 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 2254 return 2255 } 2256 if opts != nil { 2257 if err := reg.RegisterOpenAPIOptions(opts); err != nil { 2258 t.Fatalf("failed to register OpenAPI annotations: %s", err) 2259 } 2260 } 2261 result, err := applyTemplate(param{File: fileCL, reg: reg}) 2262 if err != nil { 2263 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 2264 return 2265 } 2266 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) { 2267 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2268 } 2269 2270 var response openapiResponseObject 2271 for _, v := range result.Paths { 2272 response = v.PathItemObject.Get.Responses["200"] 2273 } 2274 if want, is, name := []openapiHeadersObject{ 2275 { 2276 "String": openapiHeaderObject{ 2277 Description: "string header description", 2278 Type: "string", 2279 Format: "uuid", 2280 Pattern: "", 2281 }, 2282 "Boolean": openapiHeaderObject{ 2283 Description: "boolean header description", 2284 Type: "boolean", 2285 Default: RawExample("true"), 2286 Pattern: "^true|false$", 2287 }, 2288 "Integer": openapiHeaderObject{ 2289 Description: "integer header description", 2290 Type: "integer", 2291 Default: RawExample("0"), 2292 Pattern: "^[0-9]$", 2293 }, 2294 "Number": openapiHeaderObject{ 2295 Description: "number header description", 2296 Type: "number", 2297 Default: RawExample("1.2"), 2298 Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$", 2299 }, 2300 }, 2301 }[0], response.Headers, "response.Headers"; !reflect.DeepEqual(is, want) { 2302 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 2303 } 2304 2305 } 2306 t.Run("verify template options set via proto options", func(t *testing.T) { 2307 file := newFile() 2308 proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation) 2309 reg := descriptor.NewRegistry() 2310 verifyTemplateHeaders(t, reg, file, nil) 2311 }) 2312 } 2313 2314 func TestValidateHeaderType(t *testing.T) { 2315 type test struct { 2316 Type string 2317 Format string 2318 expectedError error 2319 } 2320 tests := []test{ 2321 { 2322 "string", 2323 "date-time", 2324 nil, 2325 }, 2326 { 2327 "boolean", 2328 "", 2329 nil, 2330 }, 2331 { 2332 "integer", 2333 "uint", 2334 nil, 2335 }, 2336 { 2337 "integer", 2338 "uint8", 2339 nil, 2340 }, 2341 { 2342 "integer", 2343 "uint16", 2344 nil, 2345 }, 2346 { 2347 "integer", 2348 "uint32", 2349 nil, 2350 }, 2351 { 2352 "integer", 2353 "uint64", 2354 nil, 2355 }, 2356 { 2357 "integer", 2358 "int", 2359 nil, 2360 }, 2361 { 2362 "integer", 2363 "int8", 2364 nil, 2365 }, 2366 { 2367 "integer", 2368 "int16", 2369 nil, 2370 }, 2371 { 2372 "integer", 2373 "int32", 2374 nil, 2375 }, 2376 { 2377 "integer", 2378 "int64", 2379 nil, 2380 }, 2381 { 2382 "integer", 2383 "float64", 2384 errors.New("the provided format \"float64\" is not a valid extension of the type \"integer\""), 2385 }, 2386 { 2387 "integer", 2388 "uuid", 2389 errors.New("the provided format \"uuid\" is not a valid extension of the type \"integer\""), 2390 }, 2391 { 2392 "number", 2393 "uint", 2394 nil, 2395 }, 2396 { 2397 "number", 2398 "uint8", 2399 nil, 2400 }, 2401 { 2402 "number", 2403 "uint16", 2404 nil, 2405 }, 2406 { 2407 "number", 2408 "uint32", 2409 nil, 2410 }, 2411 { 2412 "number", 2413 "uint64", 2414 nil, 2415 }, 2416 { 2417 "number", 2418 "int", 2419 nil, 2420 }, 2421 { 2422 "number", 2423 "int8", 2424 nil, 2425 }, 2426 { 2427 "number", 2428 "int16", 2429 nil, 2430 }, 2431 { 2432 "number", 2433 "int32", 2434 nil, 2435 }, 2436 { 2437 "number", 2438 "int64", 2439 nil, 2440 }, 2441 { 2442 "number", 2443 "float", 2444 nil, 2445 }, 2446 { 2447 "number", 2448 "float32", 2449 nil, 2450 }, 2451 { 2452 "number", 2453 "float64", 2454 nil, 2455 }, 2456 { 2457 "number", 2458 "complex64", 2459 nil, 2460 }, 2461 { 2462 "number", 2463 "complex128", 2464 nil, 2465 }, 2466 { 2467 "number", 2468 "double", 2469 nil, 2470 }, 2471 { 2472 "number", 2473 "byte", 2474 nil, 2475 }, 2476 { 2477 "number", 2478 "rune", 2479 nil, 2480 }, 2481 { 2482 "number", 2483 "uintptr", 2484 nil, 2485 }, 2486 { 2487 "number", 2488 "date", 2489 errors.New("the provided format \"date\" is not a valid extension of the type \"number\""), 2490 }, 2491 { 2492 "array", 2493 "", 2494 errors.New("the provided header type \"array\" is not supported"), 2495 }, 2496 { 2497 "foo", 2498 "", 2499 errors.New("the provided header type \"foo\" is not supported"), 2500 }, 2501 } 2502 for _, v := range tests { 2503 err := validateHeaderTypeAndFormat(v.Type, v.Format) 2504 2505 if v.expectedError == nil { 2506 if err != nil { 2507 t.Errorf("unexpected error %v", err) 2508 } 2509 } else { 2510 if err == nil { 2511 t.Fatal("expected header error not returned") 2512 } 2513 if err.Error() != v.expectedError.Error() { 2514 t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error()) 2515 } 2516 } 2517 } 2518 2519 } 2520 2521 func TestValidateDefaultValueType(t *testing.T) { 2522 type test struct { 2523 Type string 2524 Value string 2525 Format string 2526 expectedError error 2527 } 2528 tests := []test{ 2529 { 2530 "string", 2531 `"string"`, 2532 "", 2533 nil, 2534 }, 2535 { 2536 "string", 2537 "\"2012-11-01T22:08:41+00:00\"", 2538 "date-time", 2539 nil, 2540 }, 2541 { 2542 "string", 2543 "\"2012-11-01\"", 2544 "date", 2545 nil, 2546 }, 2547 { 2548 "string", 2549 "0", 2550 "", 2551 errors.New("the provided default value \"0\" does not match provider type \"string\", or is not properly quoted with escaped quotations"), 2552 }, 2553 { 2554 "string", 2555 "false", 2556 "", 2557 errors.New("the provided default value \"false\" does not match provider type \"string\", or is not properly quoted with escaped quotations"), 2558 }, 2559 { 2560 "boolean", 2561 "true", 2562 "", 2563 nil, 2564 }, 2565 { 2566 "boolean", 2567 "0", 2568 "", 2569 errors.New("the provided default value \"0\" does not match provider type \"boolean\""), 2570 }, 2571 { 2572 "boolean", 2573 `"string"`, 2574 "", 2575 errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"boolean\""), 2576 }, 2577 { 2578 "number", 2579 "1.2", 2580 "", 2581 nil, 2582 }, 2583 { 2584 "number", 2585 "123", 2586 "", 2587 nil, 2588 }, 2589 { 2590 "number", 2591 "nan", 2592 "", 2593 errors.New("the provided number \"nan\" is not a valid JSON number"), 2594 }, 2595 { 2596 "number", 2597 "NaN", 2598 "", 2599 errors.New("the provided number \"NaN\" is not a valid JSON number"), 2600 }, 2601 { 2602 "number", 2603 "-459.67", 2604 "", 2605 nil, 2606 }, 2607 { 2608 "number", 2609 "inf", 2610 "", 2611 errors.New("the provided number \"inf\" is not a valid JSON number"), 2612 }, 2613 { 2614 "number", 2615 "infinity", 2616 "", 2617 errors.New("the provided number \"infinity\" is not a valid JSON number"), 2618 }, 2619 { 2620 "number", 2621 "Inf", 2622 "", 2623 errors.New("the provided number \"Inf\" is not a valid JSON number"), 2624 }, 2625 { 2626 "number", 2627 "Infinity", 2628 "", 2629 errors.New("the provided number \"Infinity\" is not a valid JSON number"), 2630 }, 2631 { 2632 "number", 2633 "false", 2634 "", 2635 errors.New("the provided default value \"false\" does not match provider type \"number\""), 2636 }, 2637 { 2638 "number", 2639 `"string"`, 2640 "", 2641 errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"number\""), 2642 }, 2643 { 2644 "integer", 2645 "2", 2646 "", 2647 nil, 2648 }, 2649 { 2650 "integer", 2651 fmt.Sprint(math.MaxInt32), 2652 "int32", 2653 nil, 2654 }, 2655 { 2656 "integer", 2657 fmt.Sprint(math.MaxInt32 + 1), 2658 "int32", 2659 errors.New("the provided default value \"2147483648\" does not match provided format \"int32\""), 2660 }, 2661 { 2662 "integer", 2663 fmt.Sprint(math.MaxInt64), 2664 "int64", 2665 nil, 2666 }, 2667 { 2668 "integer", 2669 "9223372036854775808", 2670 "int64", 2671 errors.New("the provided default value \"9223372036854775808\" does not match provided format \"int64\""), 2672 }, 2673 { 2674 "integer", 2675 "18446744073709551615", 2676 "uint64", 2677 nil, 2678 }, 2679 { 2680 "integer", 2681 "false", 2682 "", 2683 errors.New("the provided default value \"false\" does not match provided type \"integer\""), 2684 }, 2685 { 2686 "integer", 2687 "1.2", 2688 "", 2689 errors.New("the provided default value \"1.2\" does not match provided type \"integer\""), 2690 }, 2691 { 2692 "integer", 2693 `"string"`, 2694 "", 2695 errors.New("the provided default value \"\\\"string\\\"\" does not match provided type \"integer\""), 2696 }, 2697 } 2698 for _, v := range tests { 2699 err := validateDefaultValueTypeAndFormat(v.Type, v.Value, v.Format) 2700 2701 if v.expectedError == nil { 2702 if err != nil { 2703 t.Errorf("unexpected error '%v'", err) 2704 } 2705 } else { 2706 if err == nil { 2707 t.Error("expected update error not returned") 2708 } 2709 if err.Error() != v.expectedError.Error() { 2710 t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error()) 2711 } 2712 } 2713 } 2714 2715 } 2716 2717 func TestApplyTemplateRequestWithoutClientStreaming(t *testing.T) { 2718 msgdesc := &descriptorpb.DescriptorProto{ 2719 Name: proto.String("ExampleMessage"), 2720 Field: []*descriptorpb.FieldDescriptorProto{ 2721 { 2722 Name: proto.String("nested"), 2723 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2724 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 2725 TypeName: proto.String("NestedMessage"), 2726 Number: proto.Int32(1), 2727 }, 2728 }, 2729 } 2730 nesteddesc := &descriptorpb.DescriptorProto{ 2731 Name: proto.String("NestedMessage"), 2732 Field: []*descriptorpb.FieldDescriptorProto{ 2733 { 2734 Name: proto.String("int32"), 2735 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2736 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 2737 Number: proto.Int32(1), 2738 }, 2739 { 2740 Name: proto.String("bool"), 2741 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2742 Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(), 2743 Number: proto.Int32(2), 2744 }, 2745 }, 2746 } 2747 meth := &descriptorpb.MethodDescriptorProto{ 2748 Name: proto.String("Echo"), 2749 InputType: proto.String("ExampleMessage"), 2750 OutputType: proto.String("ExampleMessage"), 2751 ClientStreaming: proto.Bool(false), 2752 } 2753 svc := &descriptorpb.ServiceDescriptorProto{ 2754 Name: proto.String("ExampleService"), 2755 Method: []*descriptorpb.MethodDescriptorProto{meth}, 2756 } 2757 2758 meth.ServerStreaming = proto.Bool(false) 2759 2760 msg := &descriptor.Message{ 2761 DescriptorProto: msgdesc, 2762 } 2763 nested := &descriptor.Message{ 2764 DescriptorProto: nesteddesc, 2765 } 2766 2767 nestedField := &descriptor.Field{ 2768 Message: msg, 2769 FieldDescriptorProto: msg.GetField()[0], 2770 } 2771 intField := &descriptor.Field{ 2772 Message: nested, 2773 FieldDescriptorProto: nested.GetField()[0], 2774 } 2775 file := descriptor.File{ 2776 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 2777 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 2778 Name: proto.String("example.proto"), 2779 Package: proto.String("example"), 2780 MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc}, 2781 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 2782 Options: &descriptorpb.FileOptions{ 2783 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 2784 }, 2785 }, 2786 GoPkg: descriptor.GoPackage{ 2787 Path: "example.com/path/to/example/example.pb", 2788 Name: "example_pb", 2789 }, 2790 Messages: []*descriptor.Message{msg, nested}, 2791 Services: []*descriptor.Service{ 2792 { 2793 ServiceDescriptorProto: svc, 2794 Methods: []*descriptor.Method{ 2795 { 2796 MethodDescriptorProto: meth, 2797 RequestType: msg, 2798 ResponseType: msg, 2799 Bindings: []*descriptor.Binding{ 2800 { 2801 HTTPMethod: "POST", 2802 PathTmpl: httprule.Template{ 2803 Version: 1, 2804 OpCodes: []int{0, 0}, 2805 Template: "/v1/echo", // TODO(achew): Figure out what this should really be 2806 }, 2807 PathParams: []descriptor.Parameter{ 2808 { 2809 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 2810 { 2811 Name: "nested", 2812 Target: nestedField, 2813 }, 2814 { 2815 Name: "int32", 2816 Target: intField, 2817 }, 2818 }), 2819 Target: intField, 2820 }, 2821 }, 2822 Body: &descriptor.Body{ 2823 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 2824 { 2825 Name: "nested", 2826 Target: nestedField, 2827 }, 2828 }), 2829 }, 2830 }, 2831 }, 2832 }, 2833 }, 2834 }, 2835 }, 2836 } 2837 reg := descriptor.NewRegistry() 2838 if err := AddErrorDefs(reg); err != nil { 2839 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 2840 return 2841 } 2842 fmt.Fprintln(os.Stderr, "fd", file.FileDescriptorProto) 2843 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 2844 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 2845 }) 2846 if err != nil { 2847 t.Fatalf("failed to load code generator request: %v", err) 2848 } 2849 fmt.Fprintln(os.Stderr, "AllFQMNs", reg.GetAllFQMNs()) 2850 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 2851 if err != nil { 2852 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 2853 return 2854 } 2855 if want, got := "2.0", result.Swagger; !reflect.DeepEqual(got, want) { 2856 t.Errorf("applyTemplate(%#v).Swagger = %s want to be %s", file, got, want) 2857 } 2858 if want, got := "", result.BasePath; !reflect.DeepEqual(got, want) { 2859 t.Errorf("applyTemplate(%#v).BasePath = %s want to be %s", file, got, want) 2860 } 2861 if want, got := ([]string)(nil), result.Schemes; !reflect.DeepEqual(got, want) { 2862 t.Errorf("applyTemplate(%#v).Schemes = %s want to be %s", file, got, want) 2863 } 2864 if want, got := []string{"application/json"}, result.Consumes; !reflect.DeepEqual(got, want) { 2865 t.Errorf("applyTemplate(%#v).Consumes = %s want to be %s", file, got, want) 2866 } 2867 if want, got := []string{"application/json"}, result.Produces; !reflect.DeepEqual(got, want) { 2868 t.Errorf("applyTemplate(%#v).Produces = %s want to be %s", file, got, want) 2869 } 2870 2871 // If there was a failure, print out the input and the json result for debugging. 2872 if t.Failed() { 2873 t.Errorf("had: %s", file) 2874 t.Errorf("got: %s", fmt.Sprint(result)) 2875 } 2876 } 2877 2878 func TestApplyTemplateRequestWithClientStreaming(t *testing.T) { 2879 msgdesc := &descriptorpb.DescriptorProto{ 2880 Name: proto.String("ExampleMessage"), 2881 Field: []*descriptorpb.FieldDescriptorProto{ 2882 { 2883 Name: proto.String("nested"), 2884 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2885 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 2886 TypeName: proto.String("NestedMessage"), 2887 Number: proto.Int32(1), 2888 }, 2889 }, 2890 } 2891 nesteddesc := &descriptorpb.DescriptorProto{ 2892 Name: proto.String("NestedMessage"), 2893 Field: []*descriptorpb.FieldDescriptorProto{ 2894 { 2895 Name: proto.String("int32"), 2896 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2897 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 2898 Number: proto.Int32(1), 2899 }, 2900 { 2901 Name: proto.String("bool"), 2902 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 2903 Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(), 2904 Number: proto.Int32(2), 2905 }, 2906 }, 2907 } 2908 meth := &descriptorpb.MethodDescriptorProto{ 2909 Name: proto.String("Echo"), 2910 InputType: proto.String("ExampleMessage"), 2911 OutputType: proto.String("ExampleMessage"), 2912 ClientStreaming: proto.Bool(true), 2913 ServerStreaming: proto.Bool(true), 2914 } 2915 svc := &descriptorpb.ServiceDescriptorProto{ 2916 Name: proto.String("ExampleService"), 2917 Method: []*descriptorpb.MethodDescriptorProto{meth}, 2918 } 2919 2920 msg := &descriptor.Message{ 2921 DescriptorProto: msgdesc, 2922 } 2923 nested := &descriptor.Message{ 2924 DescriptorProto: nesteddesc, 2925 } 2926 2927 nestedField := &descriptor.Field{ 2928 Message: msg, 2929 FieldDescriptorProto: msg.GetField()[0], 2930 } 2931 intField := &descriptor.Field{ 2932 Message: nested, 2933 FieldDescriptorProto: nested.GetField()[0], 2934 } 2935 file := descriptor.File{ 2936 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 2937 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 2938 Name: proto.String("example.proto"), 2939 Package: proto.String("example"), 2940 MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc}, 2941 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 2942 Options: &descriptorpb.FileOptions{ 2943 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 2944 }, 2945 }, 2946 GoPkg: descriptor.GoPackage{ 2947 Path: "example.com/path/to/example/example.pb", 2948 Name: "example_pb", 2949 }, 2950 Messages: []*descriptor.Message{msg, nested}, 2951 Services: []*descriptor.Service{ 2952 { 2953 ServiceDescriptorProto: svc, 2954 Methods: []*descriptor.Method{ 2955 { 2956 MethodDescriptorProto: meth, 2957 RequestType: msg, 2958 ResponseType: msg, 2959 Bindings: []*descriptor.Binding{ 2960 { 2961 HTTPMethod: "POST", 2962 PathTmpl: httprule.Template{ 2963 Version: 1, 2964 OpCodes: []int{0, 0}, 2965 Template: "/v1/echo", // TODO(achew): Figure out what this should really be 2966 }, 2967 PathParams: []descriptor.Parameter{ 2968 { 2969 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 2970 { 2971 Name: "nested", 2972 Target: nestedField, 2973 }, 2974 { 2975 Name: "int32", 2976 Target: intField, 2977 }, 2978 }), 2979 Target: intField, 2980 }, 2981 }, 2982 Body: &descriptor.Body{ 2983 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 2984 { 2985 Name: "nested", 2986 Target: nestedField, 2987 }, 2988 }), 2989 }, 2990 }, 2991 }, 2992 }, 2993 }, 2994 }, 2995 }, 2996 } 2997 reg := descriptor.NewRegistry() 2998 if err := AddErrorDefs(reg); err != nil { 2999 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3000 return 3001 } 3002 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 3003 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 3004 }) 3005 if err != nil { 3006 t.Fatalf("failed to load code generator request: %v", err) 3007 } 3008 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 3009 if err != nil { 3010 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 3011 return 3012 } 3013 3014 // Only ExampleMessage must be present, not NestedMessage 3015 if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { 3016 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3017 } 3018 if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok { 3019 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`) 3020 } else { 3021 if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) { 3022 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3023 } 3024 streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema 3025 if want, got, name := "object", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) { 3026 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3027 } 3028 if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) { 3029 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3030 } 3031 streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties) 3032 if want, got, name := 2, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) { 3033 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3034 } else { 3035 resultProperty := streamExampleExampleMessageProperties[0] 3036 if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) { 3037 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3038 } 3039 result := resultProperty.Value.(openapiSchemaObject) 3040 if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) { 3041 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3042 } 3043 errorProperty := streamExampleExampleMessageProperties[1] 3044 if want, got, name := "error", errorProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) { 3045 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3046 } 3047 err := errorProperty.Value.(openapiSchemaObject) 3048 if want, got, name := "#/definitions/rpcStatus", err.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) { 3049 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3050 } 3051 } 3052 } 3053 3054 // If there was a failure, print out the input and the json result for debugging. 3055 if t.Failed() { 3056 t.Errorf("had: %s", file) 3057 t.Errorf("got: %s", fmt.Sprint(result)) 3058 } 3059 } 3060 3061 func TestApplyTemplateRequestWithServerStreamingAndNoStandardErrors(t *testing.T) { 3062 msgdesc := &descriptorpb.DescriptorProto{ 3063 Name: proto.String("ExampleMessage"), 3064 Field: []*descriptorpb.FieldDescriptorProto{ 3065 { 3066 Name: proto.String("nested"), 3067 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3068 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 3069 TypeName: proto.String("NestedMessage"), 3070 Number: proto.Int32(1), 3071 }, 3072 }, 3073 } 3074 nesteddesc := &descriptorpb.DescriptorProto{ 3075 Name: proto.String("NestedMessage"), 3076 Field: []*descriptorpb.FieldDescriptorProto{ 3077 { 3078 Name: proto.String("int32"), 3079 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3080 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 3081 Number: proto.Int32(1), 3082 }, 3083 { 3084 Name: proto.String("bool"), 3085 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3086 Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(), 3087 Number: proto.Int32(2), 3088 }, 3089 }, 3090 } 3091 meth := &descriptorpb.MethodDescriptorProto{ 3092 Name: proto.String("Echo"), 3093 InputType: proto.String("ExampleMessage"), 3094 OutputType: proto.String("ExampleMessage"), 3095 ClientStreaming: proto.Bool(false), 3096 ServerStreaming: proto.Bool(true), 3097 } 3098 svc := &descriptorpb.ServiceDescriptorProto{ 3099 Name: proto.String("ExampleService"), 3100 Method: []*descriptorpb.MethodDescriptorProto{meth}, 3101 } 3102 3103 msg := &descriptor.Message{ 3104 DescriptorProto: msgdesc, 3105 } 3106 nested := &descriptor.Message{ 3107 DescriptorProto: nesteddesc, 3108 } 3109 3110 nestedField := &descriptor.Field{ 3111 Message: msg, 3112 FieldDescriptorProto: msg.GetField()[0], 3113 } 3114 intField := &descriptor.Field{ 3115 Message: nested, 3116 FieldDescriptorProto: nested.GetField()[0], 3117 } 3118 file := descriptor.File{ 3119 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 3120 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 3121 Name: proto.String("example.proto"), 3122 Package: proto.String("example"), 3123 MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc}, 3124 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 3125 Options: &descriptorpb.FileOptions{ 3126 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 3127 }, 3128 }, 3129 GoPkg: descriptor.GoPackage{ 3130 Path: "example.com/path/to/example/example.pb", 3131 Name: "example_pb", 3132 }, 3133 Messages: []*descriptor.Message{msg, nested}, 3134 Services: []*descriptor.Service{ 3135 { 3136 ServiceDescriptorProto: svc, 3137 Methods: []*descriptor.Method{ 3138 { 3139 MethodDescriptorProto: meth, 3140 RequestType: msg, 3141 ResponseType: msg, 3142 Bindings: []*descriptor.Binding{ 3143 { 3144 HTTPMethod: "POST", 3145 PathTmpl: httprule.Template{ 3146 Version: 1, 3147 OpCodes: []int{0, 0}, 3148 Template: "/v1/echo", 3149 }, 3150 PathParams: []descriptor.Parameter{ 3151 { 3152 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3153 { 3154 Name: "nested", 3155 Target: nestedField, 3156 }, 3157 { 3158 Name: "int32", 3159 Target: intField, 3160 }, 3161 }), 3162 Target: intField, 3163 }, 3164 }, 3165 Body: &descriptor.Body{ 3166 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3167 { 3168 Name: "nested", 3169 Target: nestedField, 3170 }, 3171 }), 3172 }, 3173 }, 3174 }, 3175 }, 3176 }, 3177 }, 3178 }, 3179 } 3180 reg := descriptor.NewRegistry() 3181 if err := AddErrorDefs(reg); err != nil { 3182 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3183 return 3184 } 3185 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 3186 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 3187 }) 3188 if err != nil { 3189 t.Fatalf("failed to load code generator request: %v", err) 3190 } 3191 reg.SetDisableDefaultErrors(true) 3192 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 3193 if err != nil { 3194 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 3195 return 3196 } 3197 3198 // Should only include the message, no status or any type 3199 if want, got, name := 1, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { 3200 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3201 } 3202 if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok { 3203 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`) 3204 } else { 3205 if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) { 3206 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3207 } 3208 streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema 3209 if want, got, name := "object", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) { 3210 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3211 } 3212 if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) { 3213 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3214 } 3215 streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties) 3216 if want, got, name := 1, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) { 3217 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3218 } else { 3219 resultProperty := streamExampleExampleMessageProperties[0] 3220 if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) { 3221 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3222 } 3223 result := resultProperty.Value.(openapiSchemaObject) 3224 if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) { 3225 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 3226 } 3227 } 3228 } 3229 3230 // If there was a failure, print out the input and the json result for debugging. 3231 if t.Failed() { 3232 t.Errorf("had: %s", file) 3233 t.Errorf("got: %s", fmt.Sprint(result)) 3234 } 3235 } 3236 3237 func TestApplyTemplateRequestWithUnusedReferences(t *testing.T) { 3238 reqdesc := &descriptorpb.DescriptorProto{ 3239 Name: proto.String("ExampleMessage"), 3240 Field: []*descriptorpb.FieldDescriptorProto{ 3241 { 3242 Name: proto.String("string"), 3243 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3244 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3245 Number: proto.Int32(1), 3246 }, 3247 }, 3248 } 3249 respdesc := &descriptorpb.DescriptorProto{ 3250 Name: proto.String("EmptyMessage"), 3251 } 3252 meth := &descriptorpb.MethodDescriptorProto{ 3253 Name: proto.String("Example"), 3254 InputType: proto.String("ExampleMessage"), 3255 OutputType: proto.String("EmptyMessage"), 3256 ClientStreaming: proto.Bool(false), 3257 ServerStreaming: proto.Bool(false), 3258 } 3259 svc := &descriptorpb.ServiceDescriptorProto{ 3260 Name: proto.String("ExampleService"), 3261 Method: []*descriptorpb.MethodDescriptorProto{meth}, 3262 } 3263 3264 req := &descriptor.Message{ 3265 DescriptorProto: reqdesc, 3266 } 3267 resp := &descriptor.Message{ 3268 DescriptorProto: respdesc, 3269 } 3270 stringField := &descriptor.Field{ 3271 Message: req, 3272 FieldDescriptorProto: req.GetField()[0], 3273 } 3274 file := descriptor.File{ 3275 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 3276 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 3277 Name: proto.String("example.proto"), 3278 Package: proto.String("example"), 3279 MessageType: []*descriptorpb.DescriptorProto{reqdesc, respdesc}, 3280 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 3281 Options: &descriptorpb.FileOptions{ 3282 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 3283 }, 3284 }, 3285 GoPkg: descriptor.GoPackage{ 3286 Path: "example.com/path/to/example/example.pb", 3287 Name: "example_pb", 3288 }, 3289 Messages: []*descriptor.Message{req, resp}, 3290 Services: []*descriptor.Service{ 3291 { 3292 ServiceDescriptorProto: svc, 3293 Methods: []*descriptor.Method{ 3294 { 3295 MethodDescriptorProto: meth, 3296 RequestType: req, 3297 ResponseType: resp, 3298 Bindings: []*descriptor.Binding{ 3299 { 3300 HTTPMethod: "GET", 3301 PathTmpl: httprule.Template{ 3302 Version: 1, 3303 OpCodes: []int{0, 0}, 3304 Template: "/v1/example", 3305 }, 3306 }, 3307 { 3308 HTTPMethod: "POST", 3309 PathTmpl: httprule.Template{ 3310 Version: 1, 3311 OpCodes: []int{0, 0}, 3312 Template: "/v1/example/{string}", 3313 }, 3314 PathParams: []descriptor.Parameter{ 3315 { 3316 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3317 { 3318 Name: "string", 3319 Target: stringField, 3320 }, 3321 }), 3322 Target: stringField, 3323 }, 3324 }, 3325 Body: &descriptor.Body{ 3326 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3327 { 3328 Name: "string", 3329 Target: stringField, 3330 }, 3331 }), 3332 }, 3333 }, 3334 }, 3335 }, 3336 }, 3337 }, 3338 }, 3339 } 3340 3341 reg := descriptor.NewRegistry() 3342 if err := AddErrorDefs(reg); err != nil { 3343 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3344 return 3345 } 3346 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 3347 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 3348 }) 3349 if err != nil { 3350 t.Fatalf("failed to load code generator request: %v", err) 3351 } 3352 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 3353 if err != nil { 3354 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 3355 return 3356 } 3357 3358 // Only EmptyMessage must be present, not ExampleMessage (plus error status) 3359 if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { 3360 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3361 } 3362 3363 // If there was a failure, print out the input and the json result for debugging. 3364 if t.Failed() { 3365 t.Errorf("had: %s", file) 3366 t.Errorf("got: %s", fmt.Sprint(result)) 3367 } 3368 } 3369 3370 func TestApplyTemplateRequestWithBodyQueryParameters(t *testing.T) { 3371 bookDesc := &descriptorpb.DescriptorProto{ 3372 Name: proto.String("Book"), 3373 Field: []*descriptorpb.FieldDescriptorProto{ 3374 { 3375 Name: proto.String("name"), 3376 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3377 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3378 Number: proto.Int32(1), 3379 }, 3380 { 3381 Name: proto.String("id"), 3382 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3383 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3384 Number: proto.Int32(2), 3385 }, 3386 }, 3387 } 3388 createDesc := &descriptorpb.DescriptorProto{ 3389 Name: proto.String("CreateBookRequest"), 3390 Field: []*descriptorpb.FieldDescriptorProto{ 3391 { 3392 Name: proto.String("parent"), 3393 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3394 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3395 Number: proto.Int32(1), 3396 }, 3397 { 3398 Name: proto.String("book"), 3399 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3400 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3401 Number: proto.Int32(2), 3402 }, 3403 { 3404 Name: proto.String("book_id"), 3405 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3406 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3407 Number: proto.Int32(3), 3408 }, 3409 }, 3410 } 3411 meth := &descriptorpb.MethodDescriptorProto{ 3412 Name: proto.String("CreateBook"), 3413 InputType: proto.String("CreateBookRequest"), 3414 OutputType: proto.String("Book"), 3415 } 3416 svc := &descriptorpb.ServiceDescriptorProto{ 3417 Name: proto.String("BookService"), 3418 Method: []*descriptorpb.MethodDescriptorProto{meth}, 3419 } 3420 3421 bookMsg := &descriptor.Message{ 3422 DescriptorProto: bookDesc, 3423 } 3424 createMsg := &descriptor.Message{ 3425 DescriptorProto: createDesc, 3426 } 3427 3428 parentField := &descriptor.Field{ 3429 Message: createMsg, 3430 FieldDescriptorProto: createMsg.GetField()[0], 3431 } 3432 bookField := &descriptor.Field{ 3433 Message: createMsg, 3434 FieldMessage: bookMsg, 3435 FieldDescriptorProto: createMsg.GetField()[1], 3436 } 3437 bookIDField := &descriptor.Field{ 3438 Message: createMsg, 3439 FieldDescriptorProto: createMsg.GetField()[2], 3440 } 3441 3442 createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField} 3443 3444 newFile := func() descriptor.File { 3445 return descriptor.File{ 3446 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 3447 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 3448 Name: proto.String("book.proto"), 3449 MessageType: []*descriptorpb.DescriptorProto{bookDesc, createDesc}, 3450 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 3451 Options: &descriptorpb.FileOptions{ 3452 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 3453 }, 3454 }, 3455 GoPkg: descriptor.GoPackage{ 3456 Path: "example.com/path/to/book.pb", 3457 Name: "book_pb", 3458 }, 3459 Messages: []*descriptor.Message{bookMsg, createMsg}, 3460 Services: []*descriptor.Service{ 3461 { 3462 ServiceDescriptorProto: svc, 3463 Methods: []*descriptor.Method{ 3464 { 3465 MethodDescriptorProto: meth, 3466 RequestType: createMsg, 3467 ResponseType: bookMsg, 3468 Bindings: []*descriptor.Binding{ 3469 { 3470 HTTPMethod: "POST", 3471 PathTmpl: httprule.Template{ 3472 Version: 1, 3473 OpCodes: []int{0, 0}, 3474 Template: "/v1/{parent=publishers/*}/books", 3475 }, 3476 PathParams: []descriptor.Parameter{ 3477 { 3478 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3479 { 3480 Name: "parent", 3481 Target: parentField, 3482 }, 3483 }), 3484 Target: parentField, 3485 }, 3486 }, 3487 Body: &descriptor.Body{ 3488 FieldPath: []descriptor.FieldPathComponent{ 3489 { 3490 Name: "book", 3491 Target: bookField, 3492 }, 3493 }, 3494 }, 3495 }, 3496 }, 3497 }, 3498 }, 3499 }, 3500 }, 3501 } 3502 } 3503 type args struct { 3504 file descriptor.File 3505 } 3506 type paramOut struct { 3507 Name string 3508 In string 3509 Required bool 3510 } 3511 tests := []struct { 3512 name string 3513 args args 3514 want []paramOut 3515 }{ 3516 { 3517 name: "book_in_body", 3518 args: args{file: newFile()}, 3519 want: []paramOut{ 3520 {"parent", "path", true}, 3521 {"book", "body", true}, 3522 {"book_id", "query", false}, 3523 }, 3524 }, 3525 { 3526 name: "book_in_query", 3527 args: args{file: func() descriptor.File { 3528 f := newFile() 3529 f.Services[0].Methods[0].Bindings[0].Body = nil 3530 return f 3531 }()}, 3532 want: []paramOut{ 3533 {"parent", "path", true}, 3534 {"book", "query", false}, 3535 {"book_id", "query", false}, 3536 }, 3537 }, 3538 } 3539 3540 for _, tt := range tests { 3541 tt := tt 3542 t.Run(tt.name, func(t *testing.T) { 3543 reg := descriptor.NewRegistry() 3544 if err := AddErrorDefs(reg); err != nil { 3545 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3546 return 3547 } 3548 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{tt.args.file.FileDescriptorProto}}) 3549 if err != nil { 3550 t.Errorf("Registry.Load() failed with %v; want success", err) 3551 return 3552 } 3553 result, err := applyTemplate(param{File: crossLinkFixture(&tt.args.file), reg: reg}) 3554 if err != nil { 3555 t.Errorf("applyTemplate(%#v) failed with %v; want success", tt.args.file, err) 3556 return 3557 } 3558 3559 if _, ok := result.getPathItemObject("/v1/{parent}/books").Post.Responses["200"]; !ok { 3560 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", tt.args.file, `result.getPathItemObject("/v1/{parent}/books").Post.Responses["200"]`) 3561 } else { 3562 3563 if want, got, name := 3, len(result.getPathItemObject("/v1/{parent}/books").Post.Parameters), `len(result.getPathItemObject("/v1/{parent}/books").Post.Parameters)`; !reflect.DeepEqual(got, want) { 3564 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", tt.args.file, name, got, want) 3565 } 3566 3567 for i, want := range tt.want { 3568 p := result.getPathItemObject("/v1/{parent}/books").Post.Parameters[i] 3569 if got, name := (paramOut{p.Name, p.In, p.Required}), `result.getPathItemObject("/v1/{parent}/books").Post.Parameters[0]`; !reflect.DeepEqual(got, want) { 3570 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", tt.args.file, name, got, want) 3571 } 3572 } 3573 3574 } 3575 3576 // If there was a failure, print out the input and the json result for debugging. 3577 if t.Failed() { 3578 t.Errorf("had: %s", tt.args.file) 3579 t.Errorf("got: %s", fmt.Sprint(result)) 3580 } 3581 }) 3582 } 3583 3584 } 3585 3586 func TestApplyTemplateWithRequestAndBodyParameters(t *testing.T) { 3587 bookDesc := &descriptorpb.DescriptorProto{ 3588 Name: proto.String("Book"), 3589 Field: []*descriptorpb.FieldDescriptorProto{ 3590 { 3591 Name: proto.String("name"), 3592 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3593 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3594 Number: proto.Int32(1), 3595 }, 3596 { 3597 Name: proto.String("id"), 3598 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3599 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3600 Number: proto.Int32(2), 3601 }, 3602 }, 3603 } 3604 createDesc := &descriptorpb.DescriptorProto{ 3605 Name: proto.String("CreateBookRequest"), 3606 Field: []*descriptorpb.FieldDescriptorProto{ 3607 { 3608 Name: proto.String("parent"), 3609 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3610 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3611 Number: proto.Int32(1), 3612 }, 3613 { 3614 Name: proto.String("book"), 3615 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3616 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3617 Number: proto.Int32(2), 3618 }, 3619 { 3620 Name: proto.String("book_id"), 3621 Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(), 3622 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3623 Number: proto.Int32(3), 3624 }, 3625 }, 3626 } 3627 meth := &descriptorpb.MethodDescriptorProto{ 3628 Name: proto.String("CreateBook"), 3629 InputType: proto.String("CreateBookRequest"), 3630 OutputType: proto.String("Book"), 3631 } 3632 svc := &descriptorpb.ServiceDescriptorProto{ 3633 Name: proto.String("BookService"), 3634 Method: []*descriptorpb.MethodDescriptorProto{meth}, 3635 } 3636 3637 bookMsg := &descriptor.Message{ 3638 DescriptorProto: bookDesc, 3639 } 3640 createMsg := &descriptor.Message{ 3641 DescriptorProto: createDesc, 3642 } 3643 3644 parentField := &descriptor.Field{ 3645 Message: createMsg, 3646 FieldDescriptorProto: createMsg.GetField()[0], 3647 } 3648 bookField := &descriptor.Field{ 3649 Message: createMsg, 3650 FieldMessage: bookMsg, 3651 FieldDescriptorProto: createMsg.GetField()[1], 3652 } 3653 bookIDField := &descriptor.Field{ 3654 Message: createMsg, 3655 FieldDescriptorProto: createMsg.GetField()[2], 3656 } 3657 3658 createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField} 3659 3660 file := descriptor.File{ 3661 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 3662 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 3663 Name: proto.String("book.proto"), 3664 MessageType: []*descriptorpb.DescriptorProto{bookDesc, createDesc}, 3665 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 3666 Options: &descriptorpb.FileOptions{ 3667 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 3668 }, 3669 }, 3670 GoPkg: descriptor.GoPackage{ 3671 Path: "example.com/path/to/book.pb", 3672 Name: "book_pb", 3673 }, 3674 Messages: []*descriptor.Message{bookMsg, createMsg}, 3675 Services: []*descriptor.Service{ 3676 { 3677 ServiceDescriptorProto: svc, 3678 Methods: []*descriptor.Method{ 3679 { 3680 MethodDescriptorProto: meth, 3681 RequestType: createMsg, 3682 ResponseType: bookMsg, 3683 Bindings: []*descriptor.Binding{ 3684 { 3685 HTTPMethod: "POST", 3686 PathTmpl: httprule.Template{ 3687 Version: 1, 3688 OpCodes: []int{0, 0}, 3689 Template: "/v1/{parent=publishers/*}/books", 3690 }, 3691 PathParams: []descriptor.Parameter{ 3692 { 3693 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 3694 { 3695 Name: "parent", 3696 Target: parentField, 3697 }, 3698 }), 3699 Target: parentField, 3700 }, 3701 }, 3702 Body: &descriptor.Body{ 3703 FieldPath: []descriptor.FieldPathComponent{}, 3704 }, 3705 }, 3706 }, 3707 }, 3708 }, 3709 }, 3710 }, 3711 } 3712 3713 reg := descriptor.NewRegistry() 3714 if err := AddErrorDefs(reg); err != nil { 3715 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3716 return 3717 } 3718 fileCL := crossLinkFixture(&file) 3719 err := reg.Load(reqFromFile(fileCL)) 3720 if err != nil { 3721 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 3722 return 3723 } 3724 result, err := applyTemplate(param{File: fileCL, reg: reg}) 3725 if err != nil { 3726 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 3727 return 3728 } 3729 if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) { 3730 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 3731 } 3732 if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) { 3733 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 3734 } 3735 if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) { 3736 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 3737 } 3738 if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) { 3739 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 3740 } 3741 if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) { 3742 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 3743 } 3744 3745 if want, is, name := 1, len(result.Paths), "len(result.Paths)"; !reflect.DeepEqual(is, want) { 3746 t.Errorf("%s = %d want to be %d", name, want, is) 3747 } 3748 if want, is, name := 4, len(result.Paths[0].PathItemObject.Post.Parameters), "len(result.Paths[0].PathItemObject.Post.Parameters)"; !reflect.DeepEqual(is, want) { 3749 t.Errorf("%s = %d want to be %d", name, want, is) 3750 } 3751 if want, is, name := "#/definitions/BookServiceCreateBookBody", result.Paths[0].PathItemObject.Post.Parameters[1].Schema.schemaCore.Ref, "result.Paths[0].PathItemObject.Post.Parameters[1].Schema.schemaCore.Ref"; !reflect.DeepEqual(is, want) { 3752 t.Errorf("%s = %s want to be %s", name, want, is) 3753 } 3754 3755 _, found := result.Definitions["BookServiceCreateBookBody"] 3756 if !found { 3757 t.Error("expecting definition to contain BookServiceCreateBookBody") 3758 } 3759 3760 // If there was a failure, print out the input and the json result for debugging. 3761 if t.Failed() { 3762 t.Errorf("had: %s", file) 3763 t.Errorf("got: %s", fmt.Sprint(result)) 3764 } 3765 } 3766 3767 // TestApplyTemplateProtobufAny tests that the protobufAny definition is correctly rendered with the @type field and 3768 // allowing additional properties. 3769 func TestApplyTemplateProtobufAny(t *testing.T) { 3770 // checkProtobufAnyFormat verifies the only property should be @type and additional properties are allowed 3771 checkProtobufAnyFormat := func(t *testing.T, protobufAny openapiSchemaObject) { 3772 anyPropsJSON, err := protobufAny.Properties.MarshalJSON() 3773 if err != nil { 3774 t.Errorf("protobufAny.Properties.MarshalJSON(), got error = %v", err) 3775 } 3776 var anyPropsMap map[string]interface{} 3777 if err := json.Unmarshal(anyPropsJSON, &anyPropsMap); err != nil { 3778 t.Errorf("json.Unmarshal(), got error = %v", err) 3779 } 3780 3781 // @type should exist 3782 if _, ok := anyPropsMap["@type"]; !ok { 3783 t.Errorf("protobufAny.Properties missing key, \"@type\". got = %#v", anyPropsMap) 3784 } 3785 3786 // and @type should be the only property 3787 if len(anyPropsMap) > 1 { 3788 t.Errorf("len(protobufAny.Properties) = %v, want = %v", len(anyPropsMap), 1) 3789 } 3790 3791 // protobufAny should have additionalProperties allowed 3792 if protobufAny.AdditionalProperties == nil { 3793 t.Errorf("protobufAny.AdditionalProperties = nil, want not-nil") 3794 } 3795 } 3796 3797 type args struct { 3798 regConfig func(registry *descriptor.Registry) 3799 msgContainsAny bool 3800 } 3801 tests := []struct { 3802 name string 3803 args args 3804 wantNumDefinitions int 3805 }{ 3806 { 3807 // our proto schema doesn't directly use protobufAny, but it is implicitly used by rpcStatus being 3808 // automatically rendered 3809 name: "default_protobufAny_from_rpcStatus", 3810 args: args{ 3811 msgContainsAny: false, 3812 }, 3813 wantNumDefinitions: 4, 3814 }, 3815 { 3816 // we have a protobufAny in a message, it should contain a ref inside the custom message 3817 name: "protobufAny_referenced_in_message", 3818 args: args{ 3819 msgContainsAny: true, 3820 }, 3821 wantNumDefinitions: 4, 3822 }, 3823 { 3824 // we have a protobufAny in a message but with automatic rendering of rpcStatus disabled 3825 name: "protobufAny_referenced_in_message_with_default_errors_disabled", 3826 args: args{ 3827 msgContainsAny: true, 3828 regConfig: func(reg *descriptor.Registry) { 3829 reg.SetDisableDefaultErrors(true) 3830 }, 3831 }, 3832 wantNumDefinitions: 3, 3833 }, 3834 { 3835 // we have a protobufAny in a message but with automatic rendering of responses disabled 3836 name: "protobufAny_referenced_in_message_with_default_responses_disabled", 3837 args: args{ 3838 msgContainsAny: true, 3839 regConfig: func(reg *descriptor.Registry) { 3840 reg.SetDisableDefaultResponses(true) 3841 }, 3842 }, 3843 wantNumDefinitions: 4, 3844 }, 3845 } 3846 3847 for _, tt := range tests { 3848 t.Run(tt.name, func(t *testing.T) { 3849 reqdesc := &descriptorpb.DescriptorProto{ 3850 Name: proto.String("ExampleMessage"), 3851 Field: []*descriptorpb.FieldDescriptorProto{ 3852 { 3853 Name: proto.String("name"), 3854 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3855 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 3856 Number: proto.Int32(1), 3857 }, 3858 }, 3859 } 3860 respdesc := &descriptorpb.DescriptorProto{ 3861 Name: proto.String("EmptyMessage"), 3862 } 3863 meth := &descriptorpb.MethodDescriptorProto{ 3864 Name: proto.String("Example"), 3865 InputType: proto.String("ExampleMessage"), 3866 OutputType: proto.String("EmptyMessage"), 3867 ClientStreaming: proto.Bool(false), 3868 ServerStreaming: proto.Bool(false), 3869 } 3870 svc := &descriptorpb.ServiceDescriptorProto{ 3871 Name: proto.String("ExampleService"), 3872 Method: []*descriptorpb.MethodDescriptorProto{meth}, 3873 } 3874 3875 req := &descriptor.Message{ 3876 DescriptorProto: reqdesc, 3877 } 3878 resp := &descriptor.Message{ 3879 DescriptorProto: respdesc, 3880 } 3881 file := descriptor.File{ 3882 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 3883 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 3884 Name: proto.String("example.proto"), 3885 Package: proto.String("example"), 3886 MessageType: []*descriptorpb.DescriptorProto{reqdesc, respdesc}, 3887 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 3888 Options: &descriptorpb.FileOptions{ 3889 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 3890 }, 3891 }, 3892 GoPkg: descriptor.GoPackage{ 3893 Path: "example.com/path/to/example/example.pb", 3894 Name: "example_pb", 3895 }, 3896 Messages: []*descriptor.Message{req, resp}, 3897 Services: []*descriptor.Service{ 3898 { 3899 ServiceDescriptorProto: svc, 3900 Methods: []*descriptor.Method{ 3901 { 3902 MethodDescriptorProto: meth, 3903 RequestType: req, 3904 ResponseType: resp, 3905 }, 3906 }, 3907 }, 3908 }, 3909 } 3910 3911 reg := descriptor.NewRegistry() 3912 reg.SetGenerateUnboundMethods(true) 3913 3914 if tt.args.regConfig != nil { 3915 tt.args.regConfig(reg) 3916 } 3917 3918 if err := AddErrorDefs(reg); err != nil { 3919 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 3920 return 3921 } 3922 3923 protoFiles := []*descriptorpb.FileDescriptorProto{ 3924 file.FileDescriptorProto, 3925 } 3926 3927 if tt.args.msgContainsAny { 3928 // add an Any field to the request message 3929 reqdesc.Field = append(reqdesc.Field, &descriptorpb.FieldDescriptorProto{ 3930 Name: proto.String("any_value"), 3931 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 3932 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 3933 TypeName: proto.String(".google.protobuf.Any"), 3934 Number: proto.Int32(2), 3935 }) 3936 3937 // update the dependencies to import it 3938 file.Dependency = append(file.Dependency, "google/protobuf/any.proto") 3939 3940 anyDescriptorProto := protodesc.ToFileDescriptorProto((&anypb.Any{}).ProtoReflect().Descriptor().ParentFile()) 3941 anyDescriptorProto.SourceCodeInfo = &descriptorpb.SourceCodeInfo{} 3942 3943 // prepend the anyDescriptorProto to the protoFiles slice so that the dependency can be resolved 3944 protoFiles = append(append(make([]*descriptorpb.FileDescriptorProto, 0, len(protoFiles)+1), anyDescriptorProto), protoFiles[0:]...) 3945 } 3946 3947 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 3948 ProtoFile: protoFiles, 3949 FileToGenerate: []string{file.GetName()}, 3950 }) 3951 if err != nil { 3952 t.Fatalf("failed to load code generator request: %v", err) 3953 } 3954 3955 target, err := reg.LookupFile(file.GetName()) 3956 if err != nil { 3957 t.Fatalf("failed to lookup file from reg: %v", err) 3958 } 3959 result, err := applyTemplate(param{File: crossLinkFixture(target), reg: reg}) 3960 if err != nil { 3961 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 3962 return 3963 } 3964 3965 if want, got, name := tt.wantNumDefinitions, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { 3966 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 3967 } 3968 3969 protobufAny, ok := result.Definitions["protobufAny"] 3970 if !ok { 3971 t.Error("expecting Definitions to contain protobufAny") 3972 } 3973 3974 checkProtobufAnyFormat(t, protobufAny) 3975 3976 // If there was a failure, print out the input and the json result for debugging. 3977 if t.Failed() { 3978 t.Errorf("had: %s", file) 3979 resultJSON, _ := json.Marshal(result) 3980 t.Errorf("got: %s", resultJSON) 3981 } 3982 }) 3983 } 3984 } 3985 3986 func generateFieldsForJSONReservedName() []*descriptor.Field { 3987 fields := make([]*descriptor.Field, 0) 3988 fieldName := "json_name" 3989 fieldJSONName := "jsonNAME" 3990 fieldDescriptor := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName} 3991 field := &descriptor.Field{FieldDescriptorProto: &fieldDescriptor} 3992 return append(fields, field) 3993 } 3994 3995 func generateMsgsForJSONReservedName() []*descriptor.Message { 3996 result := make([]*descriptor.Message, 0) 3997 // The first message, its field is field_abc and its type is NewType 3998 // NewType field_abc 3999 fieldName := "field_abc" 4000 fieldJSONName := "fieldAbc" 4001 messageName1 := "message1" 4002 messageType := "pkg.a.NewType" 4003 pfd := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName, TypeName: &messageType} 4004 result = append(result, 4005 &descriptor.Message{ 4006 DescriptorProto: &descriptorpb.DescriptorProto{ 4007 Name: &messageName1, Field: []*descriptorpb.FieldDescriptorProto{&pfd}, 4008 }, 4009 }) 4010 // The second message, its name is NewName, its type is string 4011 // message NewType { 4012 // string field_newName [json_name = RESERVEDJSONNAME] 4013 // } 4014 messageName := "NewType" 4015 field := "field_newName" 4016 fieldJSONName2 := "RESERVEDJSONNAME" 4017 pfd2 := descriptorpb.FieldDescriptorProto{Name: &field, JsonName: &fieldJSONName2} 4018 result = append(result, &descriptor.Message{ 4019 DescriptorProto: &descriptorpb.DescriptorProto{ 4020 Name: &messageName, Field: []*descriptorpb.FieldDescriptorProto{&pfd2}, 4021 }, 4022 }) 4023 return result 4024 } 4025 4026 func TestTemplateWithJsonCamelCase(t *testing.T) { 4027 var tests = []struct { 4028 input string 4029 expected string 4030 }{ 4031 {"/test/{test_id}", "/test/{testId}"}, 4032 {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1Id}/test2/{test2Id}"}, 4033 {"/test1/{test1_id}/{test2_id}", "/test1/{test1Id}/{test2Id}"}, 4034 {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1Id}/{test2Id}"}, 4035 {"/test1/{test1_id1_id2}", "/test1/{test1Id1Id2}"}, 4036 {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1Id1Id2}/test2/{test2Id3Id4}"}, 4037 {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1Id1Id2}/{test2Id3Id4}"}, 4038 {"test/{a}", "test/{a}"}, 4039 {"test/{ab}", "test/{ab}"}, 4040 {"test/{a_a}", "test/{aA}"}, 4041 {"test/{ab_c}", "test/{abC}"}, 4042 {"test/{json_name}", "test/{jsonNAME}"}, 4043 {"test/{field_abc.field_newName}", "test/{fieldAbc.RESERVEDJSONNAME}"}, 4044 } 4045 reg := descriptor.NewRegistry() 4046 reg.SetUseJSONNamesForFields(true) 4047 for _, data := range tests { 4048 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4049 if data.expected != actual { 4050 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4051 } 4052 } 4053 } 4054 4055 func TestTemplateWithoutJsonCamelCase(t *testing.T) { 4056 var tests = []struct { 4057 input string 4058 expected string 4059 }{ 4060 {"/test/{test_id}", "/test/{test_id}"}, 4061 {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1_id}/test2/{test2_id}"}, 4062 {"/test1/{test1_id}/{test2_id}", "/test1/{test1_id}/{test2_id}"}, 4063 {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1_id}/{test2_id}"}, 4064 {"/test1/{test1_id1_id2}", "/test1/{test1_id1_id2}"}, 4065 {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1_id1_id2}/test2/{test2_id3_id4}"}, 4066 {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1_id1_id2}/{test2_id3_id4}"}, 4067 {"test/{a}", "test/{a}"}, 4068 {"test/{ab}", "test/{ab}"}, 4069 {"test/{a_a}", "test/{a_a}"}, 4070 {"test/{json_name}", "test/{json_name}"}, 4071 {"test/{field_abc.field_newName}", "test/{field_abc.field_newName}"}, 4072 } 4073 reg := descriptor.NewRegistry() 4074 reg.SetUseJSONNamesForFields(false) 4075 for _, data := range tests { 4076 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4077 if data.expected != actual { 4078 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4079 } 4080 } 4081 } 4082 4083 func TestTemplateToOpenAPIPath(t *testing.T) { 4084 var tests = []struct { 4085 input string 4086 expected string 4087 }{ 4088 {"/test", "/test"}, 4089 {"/{test}", "/{test}"}, 4090 {"/{test=prefix/*}", "/{test}"}, 4091 {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"}, 4092 {"/{test1}/{test2}", "/{test1}/{test2}"}, 4093 {"/{test1}/{test2}/", "/{test1}/{test2}/"}, 4094 {"/{name=prefix/*}", "/{name}"}, 4095 {"/{name=prefix1/*/prefix2/*}", "/{name}"}, 4096 {"/{user.name=prefix/*}", "/{user.name}"}, 4097 {"/{user.name=prefix1/*/prefix2/*}", "/{user.name}"}, 4098 {"/{parent=prefix/*}/children", "/{parent}/children"}, 4099 {"/{name=prefix/*}:customMethod", "/{name}:customMethod"}, 4100 {"/{name=prefix1/*/prefix2/*}:customMethod", "/{name}:customMethod"}, 4101 {"/{user.name=prefix/*}:customMethod", "/{user.name}:customMethod"}, 4102 {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name}:customMethod"}, 4103 {"/{parent=prefix/*}/children:customMethod", "/{parent}/children:customMethod"}, 4104 } 4105 reg := descriptor.NewRegistry() 4106 reg.SetUseJSONNamesForFields(false) 4107 for _, data := range tests { 4108 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4109 if data.expected != actual { 4110 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4111 } 4112 } 4113 reg.SetUseJSONNamesForFields(true) 4114 for _, data := range tests { 4115 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4116 if data.expected != actual { 4117 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4118 } 4119 } 4120 } 4121 4122 func BenchmarkTemplateToOpenAPIPath(b *testing.B) { 4123 const input = "/{user.name=prefix1/*/prefix2/*}:customMethod" 4124 4125 b.Run("with JSON names", func(b *testing.B) { 4126 reg := descriptor.NewRegistry() 4127 reg.SetUseJSONNamesForFields(false) 4128 4129 for i := 0; i < b.N; i++ { 4130 _ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4131 } 4132 }) 4133 4134 b.Run("without JSON names", func(b *testing.B) { 4135 reg := descriptor.NewRegistry() 4136 reg.SetUseJSONNamesForFields(true) 4137 4138 for i := 0; i < b.N; i++ { 4139 _ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4140 } 4141 }) 4142 } 4143 4144 func TestResolveFullyQualifiedNameToOpenAPIName(t *testing.T) { 4145 var tests = []struct { 4146 input string 4147 output string 4148 listOfFQMNs []string 4149 namingStrategy string 4150 }{ 4151 { 4152 ".a.b.C", 4153 "C", 4154 []string{ 4155 ".a.b.C", 4156 }, 4157 "legacy", 4158 }, 4159 { 4160 ".a.b.C", 4161 "C", 4162 []string{ 4163 ".a.b.C", 4164 }, 4165 "simple", 4166 }, 4167 { 4168 ".a.b.C", 4169 "abC", 4170 []string{ 4171 ".a.C", 4172 ".a.b.C", 4173 }, 4174 "legacy", 4175 }, 4176 { 4177 ".a.b.C", 4178 "b.C", 4179 []string{ 4180 ".a.C", 4181 ".a.b.C", 4182 }, 4183 "simple", 4184 }, 4185 { 4186 ".a.b.C", 4187 "abC", 4188 []string{ 4189 ".C", 4190 ".a.C", 4191 ".a.b.C", 4192 }, 4193 "legacy", 4194 }, 4195 { 4196 ".a.b.C", 4197 "b.C", 4198 []string{ 4199 ".C", 4200 ".a.C", 4201 ".a.b.C", 4202 }, 4203 "simple", 4204 }, 4205 { 4206 ".a.b.C", 4207 "a.b.C", 4208 []string{ 4209 ".C", 4210 ".a.C", 4211 ".a.b.C", 4212 }, 4213 "fqn", 4214 }, 4215 } 4216 4217 for _, data := range tests { 4218 names := resolveFullyQualifiedNameToOpenAPINames(data.listOfFQMNs, data.namingStrategy) 4219 output := names[data.input] 4220 if output != data.output { 4221 t.Errorf("Expected fullyQualifiedNameToOpenAPIName(%v, %s) to be %s but got %s", 4222 data.input, data.namingStrategy, data.output, output) 4223 } 4224 } 4225 } 4226 4227 func templateToOpenAPIPath(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message, pathParamNames map[string]string) string { 4228 return partsToOpenAPIPath(templateToParts(path, reg, fields, msgs), pathParamNames) 4229 } 4230 4231 func templateToRegexpMap(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) map[string]string { 4232 return partsToRegexpMap(templateToParts(path, reg, fields, msgs)) 4233 } 4234 4235 func TestFQMNToRegexpMap(t *testing.T) { 4236 var tests = []struct { 4237 input string 4238 expected map[string]string 4239 }{ 4240 {"/test", map[string]string{}}, 4241 {"/{test}", map[string]string{}}, 4242 {"/{test" + pathParamUniqueSuffixDeliminator + "1=prefix/*}", map[string]string{"test" + pathParamUniqueSuffixDeliminator + "1": "prefix/[^/]+"}}, 4243 {"/{test=prefix/that/has/multiple/parts/to/it/**}", map[string]string{"test": "prefix/that/has/multiple/parts/to/it/.+"}}, 4244 {"/{test1=organizations/*}/{test2=divisions/*}", map[string]string{ 4245 "test1": "organizations/[^/]+", 4246 "test2": "divisions/[^/]+", 4247 }}, 4248 {"/v1/{name=projects/*/topics/*}:delete", map[string]string{"name": "projects/[^/]+/topics/[^/]+"}}, 4249 } 4250 reg := descriptor.NewRegistry() 4251 for _, data := range tests { 4252 actual := templateToRegexpMap(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName()) 4253 if !reflect.DeepEqual(data.expected, actual) { 4254 t.Errorf("Expected partsToRegexpMap(%v) = %v, actual: %v", data.input, data.expected, actual) 4255 } 4256 } 4257 } 4258 4259 func TestFQMNtoOpenAPIName(t *testing.T) { 4260 var tests = []struct { 4261 input string 4262 expected string 4263 }{ 4264 {"/test", "/test"}, 4265 {"/{test}", "/{test}"}, 4266 {"/{test=prefix/*}", "/{test}"}, 4267 {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"}, 4268 {"/{test1}/{test2}", "/{test1}/{test2}"}, 4269 {"/{test1}/{test2}/", "/{test1}/{test2}/"}, 4270 {"/v1/{name=tests/*}/tests", "/v1/{name}/tests"}, 4271 } 4272 reg := descriptor.NewRegistry() 4273 reg.SetUseJSONNamesForFields(false) 4274 for _, data := range tests { 4275 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4276 if data.expected != actual { 4277 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4278 } 4279 } 4280 reg.SetUseJSONNamesForFields(true) 4281 for _, data := range tests { 4282 actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string)) 4283 if data.expected != actual { 4284 t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual) 4285 } 4286 } 4287 } 4288 4289 func TestSchemaOfField(t *testing.T) { 4290 type test struct { 4291 field *descriptor.Field 4292 refs refMap 4293 expected openapiSchemaObject 4294 openAPIOptions *openapiconfig.OpenAPIOptions 4295 useJSONNamesForFields bool 4296 } 4297 4298 jsonSchema := &openapi_options.JSONSchema{ 4299 Title: "field title", 4300 Description: "field description", 4301 } 4302 jsonSchemaWithOptions := &openapi_options.JSONSchema{ 4303 Title: "field title", 4304 Description: "field description", 4305 MultipleOf: 100, 4306 Maximum: 101, 4307 ExclusiveMaximum: true, 4308 Minimum: 1, 4309 ExclusiveMinimum: true, 4310 MaxLength: 10, 4311 MinLength: 3, 4312 Pattern: "[a-z]+", 4313 MaxItems: 20, 4314 MinItems: 2, 4315 UniqueItems: true, 4316 MaxProperties: 33, 4317 MinProperties: 22, 4318 Required: []string{"req"}, 4319 ReadOnly: true, 4320 } 4321 jsonSchemaRequired := &openapi_options.JSONSchema{ 4322 Required: []string{"required_via_json_schema"}, 4323 } 4324 jsonSchemaWithFormat := &openapi_options.JSONSchema{ 4325 Format: "uuid", 4326 } 4327 4328 var fieldOptions = new(descriptorpb.FieldOptions) 4329 proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema) 4330 4331 var requiredField = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED} 4332 var requiredFieldOptions = new(descriptorpb.FieldOptions) 4333 proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField) 4334 4335 var outputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY} 4336 var outputOnlyOptions = new(descriptorpb.FieldOptions) 4337 proto.SetExtension(outputOnlyOptions, annotations.E_FieldBehavior, outputOnlyField) 4338 4339 tests := []test{ 4340 { 4341 field: &descriptor.Field{ 4342 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4343 Name: proto.String("primitive_field"), 4344 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4345 }, 4346 }, 4347 refs: make(refMap), 4348 expected: openapiSchemaObject{ 4349 schemaCore: schemaCore{ 4350 Type: "string", 4351 }, 4352 }, 4353 }, 4354 { 4355 field: &descriptor.Field{ 4356 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4357 Name: proto.String("repeated_primitive_field"), 4358 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4359 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4360 }, 4361 }, 4362 refs: make(refMap), 4363 expected: openapiSchemaObject{ 4364 schemaCore: schemaCore{ 4365 Type: "array", 4366 Items: &openapiItemsObject{ 4367 schemaCore: schemaCore{ 4368 Type: "string", 4369 }, 4370 }, 4371 }, 4372 }, 4373 }, 4374 { 4375 field: &descriptor.Field{ 4376 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4377 Name: proto.String("empty_field"), 4378 TypeName: proto.String(".google.protobuf.Empty"), 4379 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4380 }, 4381 }, 4382 refs: make(refMap), 4383 expected: openapiSchemaObject{ 4384 schemaCore: schemaCore{ 4385 Type: "object", 4386 }, 4387 Properties: &openapiSchemaObjectProperties{}, 4388 }, 4389 }, 4390 { 4391 field: &descriptor.Field{ 4392 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4393 Name: proto.String("wrapped_field"), 4394 TypeName: proto.String(".google.protobuf.FieldMask"), 4395 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4396 }, 4397 }, 4398 refs: make(refMap), 4399 expected: openapiSchemaObject{ 4400 schemaCore: schemaCore{ 4401 Type: "string", 4402 }, 4403 }, 4404 }, 4405 { 4406 field: &descriptor.Field{ 4407 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4408 Name: proto.String("wrapped_field"), 4409 TypeName: proto.String(".google.protobuf.Timestamp"), 4410 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4411 }, 4412 }, 4413 refs: make(refMap), 4414 expected: openapiSchemaObject{ 4415 schemaCore: schemaCore{ 4416 Type: "string", 4417 Format: "date-time", 4418 }, 4419 }, 4420 }, 4421 { 4422 field: &descriptor.Field{ 4423 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4424 Name: proto.String("wrapped_field"), 4425 TypeName: proto.String(".google.protobuf.Duration"), 4426 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4427 }, 4428 }, 4429 refs: make(refMap), 4430 expected: openapiSchemaObject{ 4431 schemaCore: schemaCore{ 4432 Type: "string", 4433 }, 4434 }, 4435 }, 4436 { 4437 field: &descriptor.Field{ 4438 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4439 Name: proto.String("wrapped_field"), 4440 TypeName: proto.String(".google.protobuf.StringValue"), 4441 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4442 }, 4443 }, 4444 refs: make(refMap), 4445 expected: openapiSchemaObject{ 4446 schemaCore: schemaCore{ 4447 Type: "string", 4448 }, 4449 }, 4450 }, 4451 { 4452 field: &descriptor.Field{ 4453 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4454 Name: proto.String("repeated_wrapped_field"), 4455 TypeName: proto.String(".google.protobuf.StringValue"), 4456 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4457 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4458 }, 4459 }, 4460 refs: make(refMap), 4461 expected: openapiSchemaObject{ 4462 schemaCore: schemaCore{ 4463 Type: "array", 4464 Items: &openapiItemsObject{ 4465 schemaCore: schemaCore{ 4466 Type: "string", 4467 }, 4468 }, 4469 }, 4470 }, 4471 }, 4472 { 4473 field: &descriptor.Field{ 4474 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4475 Name: proto.String("wrapped_field"), 4476 TypeName: proto.String(".google.protobuf.BytesValue"), 4477 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4478 }, 4479 }, 4480 refs: make(refMap), 4481 expected: openapiSchemaObject{ 4482 schemaCore: schemaCore{ 4483 Type: "string", 4484 Format: "byte", 4485 }, 4486 }, 4487 }, 4488 { 4489 field: &descriptor.Field{ 4490 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4491 Name: proto.String("wrapped_field"), 4492 TypeName: proto.String(".google.protobuf.Int32Value"), 4493 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4494 }, 4495 }, 4496 refs: make(refMap), 4497 expected: openapiSchemaObject{ 4498 schemaCore: schemaCore{ 4499 Type: "integer", 4500 Format: "int32", 4501 }, 4502 }, 4503 }, 4504 { 4505 field: &descriptor.Field{ 4506 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4507 Name: proto.String("wrapped_field"), 4508 TypeName: proto.String(".google.protobuf.UInt32Value"), 4509 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4510 }, 4511 }, 4512 refs: make(refMap), 4513 expected: openapiSchemaObject{ 4514 schemaCore: schemaCore{ 4515 Type: "integer", 4516 Format: "int64", 4517 }, 4518 }, 4519 }, 4520 { 4521 field: &descriptor.Field{ 4522 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4523 Name: proto.String("wrapped_field"), 4524 TypeName: proto.String(".google.protobuf.Int64Value"), 4525 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4526 }, 4527 }, 4528 refs: make(refMap), 4529 expected: openapiSchemaObject{ 4530 schemaCore: schemaCore{ 4531 Type: "string", 4532 Format: "int64", 4533 }, 4534 }, 4535 }, 4536 { 4537 field: &descriptor.Field{ 4538 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4539 Name: proto.String("wrapped_field"), 4540 TypeName: proto.String(".google.protobuf.UInt64Value"), 4541 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4542 }, 4543 }, 4544 refs: make(refMap), 4545 expected: openapiSchemaObject{ 4546 schemaCore: schemaCore{ 4547 Type: "string", 4548 Format: "uint64", 4549 }, 4550 }, 4551 }, 4552 { 4553 field: &descriptor.Field{ 4554 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4555 Name: proto.String("wrapped_field"), 4556 TypeName: proto.String(".google.protobuf.FloatValue"), 4557 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4558 }, 4559 }, 4560 refs: make(refMap), 4561 expected: openapiSchemaObject{ 4562 schemaCore: schemaCore{ 4563 Type: "number", 4564 Format: "float", 4565 }, 4566 }, 4567 }, 4568 { 4569 field: &descriptor.Field{ 4570 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4571 Name: proto.String("wrapped_field"), 4572 TypeName: proto.String(".google.protobuf.DoubleValue"), 4573 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4574 }, 4575 }, 4576 refs: make(refMap), 4577 expected: openapiSchemaObject{ 4578 schemaCore: schemaCore{ 4579 Type: "number", 4580 Format: "double", 4581 }, 4582 }, 4583 }, 4584 { 4585 field: &descriptor.Field{ 4586 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4587 Name: proto.String("wrapped_field"), 4588 TypeName: proto.String(".google.protobuf.BoolValue"), 4589 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4590 }, 4591 }, 4592 refs: make(refMap), 4593 expected: openapiSchemaObject{ 4594 schemaCore: schemaCore{ 4595 Type: "boolean", 4596 }, 4597 }, 4598 }, 4599 { 4600 field: &descriptor.Field{ 4601 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4602 Name: proto.String("wrapped_field"), 4603 TypeName: proto.String(".google.protobuf.Struct"), 4604 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4605 }, 4606 }, 4607 refs: make(refMap), 4608 expected: openapiSchemaObject{ 4609 schemaCore: schemaCore{ 4610 Type: "object", 4611 }, 4612 }, 4613 }, 4614 { 4615 field: &descriptor.Field{ 4616 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4617 Name: proto.String("wrapped_field"), 4618 TypeName: proto.String(".google.protobuf.Value"), 4619 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4620 }, 4621 }, 4622 refs: make(refMap), 4623 expected: openapiSchemaObject{ 4624 schemaCore: schemaCore{}, 4625 }, 4626 }, 4627 { 4628 field: &descriptor.Field{ 4629 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4630 Name: proto.String("wrapped_field"), 4631 TypeName: proto.String(".google.protobuf.ListValue"), 4632 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4633 }, 4634 }, 4635 refs: make(refMap), 4636 expected: openapiSchemaObject{ 4637 schemaCore: schemaCore{ 4638 Type: "array", 4639 Items: &openapiItemsObject{schemaCore: schemaCore{ 4640 Type: "object", 4641 }}, 4642 }, 4643 }, 4644 }, 4645 { 4646 field: &descriptor.Field{ 4647 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4648 Name: proto.String("wrapped_field"), 4649 TypeName: proto.String(".google.protobuf.NullValue"), 4650 Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(), 4651 }, 4652 }, 4653 refs: make(refMap), 4654 expected: openapiSchemaObject{ 4655 schemaCore: schemaCore{ 4656 Type: "string", 4657 }, 4658 }, 4659 }, 4660 { 4661 field: &descriptor.Field{ 4662 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4663 Name: proto.String("message_field"), 4664 TypeName: proto.String(".example.Message"), 4665 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4666 }, 4667 }, 4668 refs: refMap{".example.Message": struct{}{}}, 4669 expected: openapiSchemaObject{ 4670 schemaCore: schemaCore{ 4671 Ref: "#/definitions/exampleMessage", 4672 }, 4673 }, 4674 }, 4675 { 4676 field: &descriptor.Field{ 4677 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4678 Name: proto.String("map_field"), 4679 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4680 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4681 TypeName: proto.String(".example.Message.MapFieldEntry"), 4682 Options: fieldOptions, 4683 }, 4684 }, 4685 refs: make(refMap), 4686 expected: openapiSchemaObject{ 4687 schemaCore: schemaCore{ 4688 Type: "object", 4689 }, 4690 AdditionalProperties: &openapiSchemaObject{ 4691 schemaCore: schemaCore{Type: "string"}, 4692 }, 4693 Title: "field title", 4694 Description: "field description", 4695 }, 4696 }, 4697 { 4698 field: &descriptor.Field{ 4699 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4700 Name: proto.String("array_field"), 4701 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4702 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4703 Options: fieldOptions, 4704 }, 4705 }, 4706 refs: make(refMap), 4707 expected: openapiSchemaObject{ 4708 schemaCore: schemaCore{ 4709 Type: "array", 4710 Items: &openapiItemsObject{schemaCore: schemaCore{ 4711 Type: "string", 4712 }}, 4713 }, 4714 Title: "field title", 4715 Description: "field description", 4716 }, 4717 }, 4718 { 4719 field: &descriptor.Field{ 4720 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4721 Name: proto.String("primitive_field"), 4722 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 4723 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 4724 Options: fieldOptions, 4725 }, 4726 }, 4727 refs: make(refMap), 4728 expected: openapiSchemaObject{ 4729 schemaCore: schemaCore{ 4730 Type: "integer", 4731 Format: "int32", 4732 }, 4733 Title: "field title", 4734 Description: "field description", 4735 }, 4736 }, 4737 { 4738 field: &descriptor.Field{ 4739 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4740 Name: proto.String("message_field"), 4741 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 4742 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4743 TypeName: proto.String(".example.Empty"), 4744 Options: fieldOptions, 4745 }, 4746 }, 4747 refs: refMap{".example.Empty": struct{}{}}, 4748 expected: openapiSchemaObject{ 4749 schemaCore: schemaCore{ 4750 Ref: "#/definitions/exampleEmpty", 4751 }, 4752 Title: "field title", 4753 Description: "field description", 4754 }, 4755 }, 4756 { 4757 field: &descriptor.Field{ 4758 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4759 Name: proto.String("map_field"), // should be called map_field_option but it's not valid map field name 4760 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4761 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4762 TypeName: proto.String(".example.Message.MapFieldEntry"), 4763 }, 4764 }, 4765 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4766 Field: []*openapiconfig.OpenAPIFieldOption{ 4767 { 4768 Field: "example.Message.map_field", 4769 Option: jsonSchema, 4770 }, 4771 }, 4772 }, 4773 refs: make(refMap), 4774 expected: openapiSchemaObject{ 4775 schemaCore: schemaCore{ 4776 Type: "object", 4777 }, 4778 AdditionalProperties: &openapiSchemaObject{ 4779 schemaCore: schemaCore{Type: "string"}, 4780 }, 4781 Title: "field title", 4782 Description: "field description", 4783 }, 4784 }, 4785 { 4786 field: &descriptor.Field{ 4787 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4788 Name: proto.String("array_field_option"), 4789 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4790 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4791 }, 4792 }, 4793 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4794 Field: []*openapiconfig.OpenAPIFieldOption{ 4795 { 4796 Field: "example.Message.array_field_option", 4797 Option: jsonSchema, 4798 }, 4799 }, 4800 }, 4801 refs: make(refMap), 4802 expected: openapiSchemaObject{ 4803 schemaCore: schemaCore{ 4804 Type: "array", 4805 Items: &openapiItemsObject{schemaCore: schemaCore{ 4806 Type: "string", 4807 }}, 4808 }, 4809 Title: "field title", 4810 Description: "field description", 4811 }, 4812 }, 4813 { 4814 field: &descriptor.Field{ 4815 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4816 Name: proto.String("primitive_field_option"), 4817 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 4818 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 4819 }, 4820 }, 4821 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4822 Field: []*openapiconfig.OpenAPIFieldOption{ 4823 { 4824 Field: "example.Message.primitive_field_option", 4825 Option: jsonSchema, 4826 }, 4827 }, 4828 }, 4829 refs: make(refMap), 4830 expected: openapiSchemaObject{ 4831 schemaCore: schemaCore{ 4832 Type: "integer", 4833 Format: "int32", 4834 }, 4835 Title: "field title", 4836 Description: "field description", 4837 }, 4838 }, 4839 { 4840 field: &descriptor.Field{ 4841 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4842 Name: proto.String("primitive_field_option"), 4843 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 4844 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum().Enum(), 4845 }, 4846 }, 4847 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4848 Field: []*openapiconfig.OpenAPIFieldOption{ 4849 { 4850 Field: "example.Message.primitive_field_option", 4851 Option: &openapi_options.JSONSchema{ 4852 Title: "field title", 4853 Description: "field description", 4854 Format: "uuid", 4855 }, 4856 }, 4857 }, 4858 }, 4859 refs: make(refMap), 4860 expected: openapiSchemaObject{ 4861 schemaCore: schemaCore{ 4862 Type: "string", 4863 Format: "uuid", 4864 }, 4865 Title: "field title", 4866 Description: "field description", 4867 }, 4868 }, 4869 { 4870 field: &descriptor.Field{ 4871 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4872 Name: proto.String("message_field_option"), 4873 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 4874 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4875 TypeName: proto.String(".example.Empty"), 4876 }, 4877 }, 4878 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4879 Field: []*openapiconfig.OpenAPIFieldOption{ 4880 { 4881 Field: "example.Message.message_field_option", 4882 Option: jsonSchema, 4883 }, 4884 }, 4885 }, 4886 refs: refMap{".example.Empty": struct{}{}}, 4887 expected: openapiSchemaObject{ 4888 schemaCore: schemaCore{ 4889 Ref: "#/definitions/exampleEmpty", 4890 }, 4891 Title: "field title", 4892 Description: "field description", 4893 }, 4894 }, 4895 { 4896 field: &descriptor.Field{ 4897 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4898 Name: proto.String("required_via_field_behavior_field"), 4899 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4900 Options: requiredFieldOptions, 4901 }, 4902 }, 4903 refs: make(refMap), 4904 expected: openapiSchemaObject{ 4905 schemaCore: schemaCore{ 4906 Type: "string", 4907 }, 4908 Required: []string{"required_via_field_behavior_field"}, 4909 }, 4910 }, 4911 { 4912 field: &descriptor.Field{ 4913 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4914 Name: proto.String("readonly_via_field_behavior_field"), 4915 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4916 Options: outputOnlyOptions, 4917 }, 4918 }, 4919 refs: make(refMap), 4920 expected: openapiSchemaObject{ 4921 schemaCore: schemaCore{ 4922 Type: "string", 4923 }, 4924 ReadOnly: true, 4925 }, 4926 }, 4927 { 4928 field: &descriptor.Field{ 4929 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4930 Name: proto.String("required_message_field"), 4931 TypeName: proto.String(".example.Message"), 4932 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 4933 Options: requiredFieldOptions, 4934 }, 4935 }, 4936 refs: refMap{".example.Message": struct{}{}}, 4937 expected: openapiSchemaObject{ 4938 schemaCore: schemaCore{ 4939 Ref: "#/definitions/exampleMessage", 4940 }, 4941 Required: []string{"required_message_field"}, 4942 }, 4943 }, 4944 { 4945 field: &descriptor.Field{ 4946 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4947 Name: proto.String("array_field_option"), 4948 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4949 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 4950 }, 4951 }, 4952 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4953 Field: []*openapiconfig.OpenAPIFieldOption{ 4954 { 4955 Field: "example.Message.array_field_option", 4956 Option: jsonSchemaWithOptions, 4957 }, 4958 }, 4959 }, 4960 refs: make(refMap), 4961 expected: openapiSchemaObject{ 4962 schemaCore: schemaCore{ 4963 Type: "array", 4964 Items: &openapiItemsObject{ 4965 schemaCore: schemaCore{ 4966 Type: "string", 4967 }, 4968 MultipleOf: 100, 4969 Maximum: 101, 4970 ExclusiveMaximum: true, 4971 Minimum: 1, 4972 ExclusiveMinimum: true, 4973 MaxLength: 10, 4974 MinLength: 3, 4975 Pattern: "[a-z]+", 4976 UniqueItems: true, 4977 MaxProperties: 33, 4978 MinProperties: 22, 4979 Required: []string{"req"}, 4980 ReadOnly: true, 4981 }, 4982 }, 4983 Title: "field title", 4984 Description: "field description", 4985 MaxItems: 20, 4986 MinItems: 2, 4987 }, 4988 }, 4989 { 4990 field: &descriptor.Field{ 4991 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 4992 Name: proto.String("array_field_option"), 4993 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 4994 Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(), 4995 }, 4996 }, 4997 openAPIOptions: &openapiconfig.OpenAPIOptions{ 4998 Field: []*openapiconfig.OpenAPIFieldOption{ 4999 { 5000 Field: "example.Message.array_field_option", 5001 Option: jsonSchemaWithOptions, 5002 }, 5003 }, 5004 }, 5005 refs: make(refMap), 5006 expected: openapiSchemaObject{ 5007 schemaCore: schemaCore{ 5008 Type: "array", 5009 Items: &openapiItemsObject{ 5010 schemaCore: schemaCore{ 5011 Type: "string", 5012 Format: "int64", 5013 }, 5014 MultipleOf: 100, 5015 Maximum: 101, 5016 ExclusiveMaximum: true, 5017 Minimum: 1, 5018 ExclusiveMinimum: true, 5019 MaxLength: 10, 5020 MinLength: 3, 5021 Pattern: "[a-z]+", 5022 UniqueItems: true, 5023 MaxProperties: 33, 5024 MinProperties: 22, 5025 Required: []string{"req"}, 5026 ReadOnly: true, 5027 }, 5028 }, 5029 Title: "field title", 5030 Description: "field description", 5031 MaxItems: 20, 5032 MinItems: 2, 5033 }, 5034 }, 5035 { 5036 field: &descriptor.Field{ 5037 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 5038 Name: proto.String("array_field_format"), 5039 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 5040 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5041 }, 5042 }, 5043 openAPIOptions: &openapiconfig.OpenAPIOptions{ 5044 Field: []*openapiconfig.OpenAPIFieldOption{ 5045 { 5046 Field: "example.Message.array_field_format", 5047 Option: jsonSchemaWithFormat, 5048 }, 5049 }, 5050 }, 5051 refs: make(refMap), 5052 expected: openapiSchemaObject{ 5053 schemaCore: schemaCore{ 5054 Type: "array", 5055 Items: &openapiItemsObject{ 5056 schemaCore: schemaCore{ 5057 Type: "string", 5058 Format: "uuid", 5059 }, 5060 }, 5061 }, 5062 }, 5063 }, 5064 { 5065 field: &descriptor.Field{ 5066 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 5067 Name: proto.String("required_via_field_behavior_field_json_name"), 5068 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5069 JsonName: proto.String("required_field_custom_name"), 5070 Options: requiredFieldOptions, 5071 }, 5072 }, 5073 refs: make(refMap), 5074 expected: openapiSchemaObject{ 5075 schemaCore: schemaCore{ 5076 Type: "string", 5077 }, 5078 Required: []string{"required_field_custom_name"}, 5079 }, 5080 useJSONNamesForFields: true, 5081 }, 5082 { 5083 field: &descriptor.Field{ 5084 FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{ 5085 Name: proto.String("required_via_json_schema"), 5086 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5087 JsonName: proto.String("required_via_json_schema_json_name"), 5088 }, 5089 }, 5090 openAPIOptions: &openapiconfig.OpenAPIOptions{ 5091 Field: []*openapiconfig.OpenAPIFieldOption{ 5092 { 5093 Field: "example.Message.required_via_json_schema", 5094 Option: jsonSchemaRequired, 5095 }, 5096 }, 5097 }, 5098 refs: make(refMap), 5099 useJSONNamesForFields: true, 5100 expected: openapiSchemaObject{ 5101 schemaCore: schemaCore{ 5102 Type: "string", 5103 }, 5104 Required: []string{"required_via_json_schema_json_name"}, 5105 }, 5106 }, 5107 } 5108 for _, test := range tests { 5109 reg := descriptor.NewRegistry() 5110 reg.SetUseJSONNamesForFields(test.useJSONNamesForFields) 5111 5112 req := &pluginpb.CodeGeneratorRequest{ 5113 ProtoFile: []*descriptorpb.FileDescriptorProto{ 5114 { 5115 Name: proto.String("third_party/google.proto"), 5116 Package: proto.String("google.protobuf"), 5117 Options: &descriptorpb.FileOptions{ 5118 GoPackage: proto.String("third_party/google"), 5119 }, 5120 MessageType: []*descriptorpb.DescriptorProto{ 5121 protodesc.ToDescriptorProto((&emptypb.Empty{}).ProtoReflect().Descriptor()), 5122 protodesc.ToDescriptorProto((&structpb.Struct{}).ProtoReflect().Descriptor()), 5123 protodesc.ToDescriptorProto((&structpb.Value{}).ProtoReflect().Descriptor()), 5124 protodesc.ToDescriptorProto((&structpb.ListValue{}).ProtoReflect().Descriptor()), 5125 protodesc.ToDescriptorProto((&field_mask.FieldMask{}).ProtoReflect().Descriptor()), 5126 protodesc.ToDescriptorProto((×tamppb.Timestamp{}).ProtoReflect().Descriptor()), 5127 protodesc.ToDescriptorProto((&durationpb.Duration{}).ProtoReflect().Descriptor()), 5128 protodesc.ToDescriptorProto((&wrapperspb.StringValue{}).ProtoReflect().Descriptor()), 5129 protodesc.ToDescriptorProto((&wrapperspb.BytesValue{}).ProtoReflect().Descriptor()), 5130 protodesc.ToDescriptorProto((&wrapperspb.Int32Value{}).ProtoReflect().Descriptor()), 5131 protodesc.ToDescriptorProto((&wrapperspb.UInt32Value{}).ProtoReflect().Descriptor()), 5132 protodesc.ToDescriptorProto((&wrapperspb.Int64Value{}).ProtoReflect().Descriptor()), 5133 protodesc.ToDescriptorProto((&wrapperspb.UInt64Value{}).ProtoReflect().Descriptor()), 5134 protodesc.ToDescriptorProto((&wrapperspb.FloatValue{}).ProtoReflect().Descriptor()), 5135 protodesc.ToDescriptorProto((&wrapperspb.DoubleValue{}).ProtoReflect().Descriptor()), 5136 protodesc.ToDescriptorProto((&wrapperspb.BoolValue{}).ProtoReflect().Descriptor()), 5137 }, 5138 EnumType: []*descriptorpb.EnumDescriptorProto{ 5139 protodesc.ToEnumDescriptorProto(structpb.NullValue(0).Descriptor()), 5140 }, 5141 }, 5142 { 5143 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 5144 Name: proto.String("example.proto"), 5145 Package: proto.String("example"), 5146 Dependency: []string{"third_party/google.proto"}, 5147 Options: &descriptorpb.FileOptions{ 5148 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 5149 }, 5150 MessageType: []*descriptorpb.DescriptorProto{ 5151 { 5152 Name: proto.String("Message"), 5153 Field: []*descriptorpb.FieldDescriptorProto{ 5154 { 5155 Name: proto.String("value"), 5156 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5157 Number: proto.Int32(1), 5158 }, 5159 func() *descriptorpb.FieldDescriptorProto { 5160 fd := test.field.FieldDescriptorProto 5161 fd.Number = proto.Int32(2) 5162 return fd 5163 }(), 5164 }, 5165 NestedType: []*descriptorpb.DescriptorProto{ 5166 { 5167 Name: proto.String("MapFieldEntry"), 5168 Options: &descriptorpb.MessageOptions{MapEntry: proto.Bool(true)}, 5169 Field: []*descriptorpb.FieldDescriptorProto{ 5170 { 5171 Name: proto.String("key"), 5172 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 5173 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5174 Number: proto.Int32(1), 5175 }, 5176 { 5177 Name: proto.String("value"), 5178 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 5179 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5180 Number: proto.Int32(2), 5181 }, 5182 }, 5183 }, 5184 }, 5185 }, 5186 { 5187 Name: proto.String("Empty"), 5188 }, 5189 }, 5190 EnumType: []*descriptorpb.EnumDescriptorProto{ 5191 { 5192 Name: proto.String("MessageType"), 5193 Value: []*descriptorpb.EnumValueDescriptorProto{ 5194 { 5195 Name: proto.String("MESSAGE_TYPE_1"), 5196 Number: proto.Int32(0), 5197 }, 5198 }, 5199 }, 5200 }, 5201 Service: []*descriptorpb.ServiceDescriptorProto{}, 5202 }, 5203 }, 5204 } 5205 err := reg.Load(req) 5206 if err != nil { 5207 t.Errorf("failed to reg.Load(req): %v", err) 5208 } 5209 5210 // set field's parent message pointer to message so field can resolve its FQFN 5211 test.field.Message = &descriptor.Message{ 5212 DescriptorProto: req.ProtoFile[1].MessageType[0], 5213 File: &descriptor.File{ 5214 FileDescriptorProto: req.ProtoFile[1], 5215 }, 5216 } 5217 5218 if test.openAPIOptions != nil { 5219 if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil { 5220 t.Fatalf("failed to register OpenAPI options: %s", err) 5221 } 5222 } 5223 5224 refs := make(refMap) 5225 actual := schemaOfField(test.field, reg, refs) 5226 expectedSchemaObject := test.expected 5227 if e, a := expectedSchemaObject, actual; !reflect.DeepEqual(a, e) { 5228 t.Errorf("Expected schemaOfField(%v) = \n%#+v, actual: \n%#+v", test.field, e, a) 5229 } 5230 if !reflect.DeepEqual(refs, test.refs) { 5231 t.Errorf("Expected schemaOfField(%v) to add refs %v, not %v", test.field, test.refs, refs) 5232 } 5233 } 5234 } 5235 5236 func TestRenderMessagesAsDefinition(t *testing.T) { 5237 jsonSchema := &openapi_options.JSONSchema{ 5238 Title: "field title", 5239 Description: "field description", 5240 Required: []string{"aRequiredField"}, 5241 } 5242 5243 var requiredField = new(descriptorpb.FieldOptions) 5244 proto.SetExtension(requiredField, openapi_options.E_Openapiv2Field, jsonSchema) 5245 5246 var fieldBehaviorRequired = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED} 5247 var requiredFieldOptions = new(descriptorpb.FieldOptions) 5248 proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, fieldBehaviorRequired) 5249 5250 var fieldBehaviorOutputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY} 5251 var fieldBehaviorOutputOnlyOptions = new(descriptorpb.FieldOptions) 5252 proto.SetExtension(fieldBehaviorOutputOnlyOptions, annotations.E_FieldBehavior, fieldBehaviorOutputOnlyField) 5253 5254 var fieldVisibilityFieldInternal = &visibility.VisibilityRule{Restriction: "INTERNAL"} 5255 var fieldVisibilityInternalOption = new(descriptorpb.FieldOptions) 5256 proto.SetExtension(fieldVisibilityInternalOption, visibility.E_FieldVisibility, fieldVisibilityFieldInternal) 5257 5258 var fieldVisibilityFieldPreview = &visibility.VisibilityRule{Restriction: "INTERNAL,PREVIEW"} 5259 var fieldVisibilityPreviewOption = new(descriptorpb.FieldOptions) 5260 proto.SetExtension(fieldVisibilityPreviewOption, visibility.E_FieldVisibility, fieldVisibilityFieldPreview) 5261 5262 tests := []struct { 5263 descr string 5264 msgDescs []*descriptorpb.DescriptorProto 5265 schema map[string]*openapi_options.Schema // per-message schema to add 5266 defs openapiDefinitionsObject 5267 openAPIOptions *openapiconfig.OpenAPIOptions 5268 pathParams []descriptor.Parameter 5269 UseJSONNamesForFields bool 5270 UseAllOfForRefs bool 5271 }{ 5272 { 5273 descr: "no OpenAPI options", 5274 msgDescs: []*descriptorpb.DescriptorProto{ 5275 {Name: proto.String("Message")}, 5276 }, 5277 schema: map[string]*openapi_options.Schema{}, 5278 defs: map[string]openapiSchemaObject{ 5279 "Message": {schemaCore: schemaCore{Type: "object"}}, 5280 }, 5281 }, 5282 { 5283 descr: "example option", 5284 msgDescs: []*descriptorpb.DescriptorProto{ 5285 {Name: proto.String("Message")}, 5286 }, 5287 schema: map[string]*openapi_options.Schema{ 5288 "Message": { 5289 Example: `{"foo":"bar"}`, 5290 }, 5291 }, 5292 defs: map[string]openapiSchemaObject{ 5293 "Message": {schemaCore: schemaCore{ 5294 Type: "object", 5295 Example: RawExample(`{"foo":"bar"}`), 5296 }}, 5297 }, 5298 }, 5299 { 5300 descr: "example option with something non-json", 5301 msgDescs: []*descriptorpb.DescriptorProto{ 5302 {Name: proto.String("Message")}, 5303 }, 5304 schema: map[string]*openapi_options.Schema{ 5305 "Message": { 5306 Example: `XXXX anything goes XXXX`, 5307 }, 5308 }, 5309 defs: map[string]openapiSchemaObject{ 5310 "Message": {schemaCore: schemaCore{ 5311 Type: "object", 5312 Example: RawExample(`XXXX anything goes XXXX`), 5313 }}, 5314 }, 5315 }, 5316 { 5317 descr: "external docs option", 5318 msgDescs: []*descriptorpb.DescriptorProto{ 5319 {Name: proto.String("Message")}, 5320 }, 5321 schema: map[string]*openapi_options.Schema{ 5322 "Message": { 5323 ExternalDocs: &openapi_options.ExternalDocumentation{ 5324 Description: "glorious docs", 5325 Url: "https://nada", 5326 }, 5327 }, 5328 }, 5329 defs: map[string]openapiSchemaObject{ 5330 "Message": { 5331 schemaCore: schemaCore{ 5332 Type: "object", 5333 }, 5334 ExternalDocs: &openapiExternalDocumentationObject{ 5335 Description: "glorious docs", 5336 URL: "https://nada", 5337 }, 5338 }, 5339 }, 5340 }, 5341 { 5342 descr: "JSONSchema options", 5343 msgDescs: []*descriptorpb.DescriptorProto{ 5344 {Name: proto.String("Message")}, 5345 }, 5346 schema: map[string]*openapi_options.Schema{ 5347 "Message": { 5348 JsonSchema: &openapi_options.JSONSchema{ 5349 Title: "title", 5350 Description: "desc", 5351 MultipleOf: 100, 5352 Maximum: 101, 5353 ExclusiveMaximum: true, 5354 Minimum: 1, 5355 ExclusiveMinimum: true, 5356 MaxLength: 10, 5357 MinLength: 3, 5358 Pattern: "[a-z]+", 5359 MaxItems: 20, 5360 MinItems: 2, 5361 UniqueItems: true, 5362 MaxProperties: 33, 5363 MinProperties: 22, 5364 Required: []string{"req"}, 5365 ReadOnly: true, 5366 }, 5367 }, 5368 }, 5369 defs: map[string]openapiSchemaObject{ 5370 "Message": { 5371 schemaCore: schemaCore{ 5372 Type: "object", 5373 }, 5374 Title: "title", 5375 Description: "desc", 5376 MultipleOf: 100, 5377 Maximum: 101, 5378 ExclusiveMaximum: true, 5379 Minimum: 1, 5380 ExclusiveMinimum: true, 5381 MaxLength: 10, 5382 MinLength: 3, 5383 Pattern: "[a-z]+", 5384 MaxItems: 20, 5385 MinItems: 2, 5386 UniqueItems: true, 5387 MaxProperties: 33, 5388 MinProperties: 22, 5389 Required: []string{"req"}, 5390 ReadOnly: true, 5391 }, 5392 }, 5393 }, 5394 { 5395 descr: "JSONSchema options from registry", 5396 msgDescs: []*descriptorpb.DescriptorProto{ 5397 {Name: proto.String("Message")}, 5398 }, 5399 openAPIOptions: &openapiconfig.OpenAPIOptions{ 5400 Message: []*openapiconfig.OpenAPIMessageOption{ 5401 { 5402 Message: "example.Message", 5403 Option: &openapi_options.Schema{ 5404 JsonSchema: &openapi_options.JSONSchema{ 5405 Title: "title", 5406 Description: "desc", 5407 MultipleOf: 100, 5408 Maximum: 101, 5409 ExclusiveMaximum: true, 5410 Minimum: 1, 5411 ExclusiveMinimum: true, 5412 MaxLength: 10, 5413 MinLength: 3, 5414 Pattern: "[a-z]+", 5415 MaxItems: 20, 5416 MinItems: 2, 5417 UniqueItems: true, 5418 MaxProperties: 33, 5419 MinProperties: 22, 5420 Required: []string{"req"}, 5421 ReadOnly: true, 5422 }, 5423 }, 5424 }, 5425 }, 5426 }, 5427 defs: map[string]openapiSchemaObject{ 5428 "Message": { 5429 schemaCore: schemaCore{ 5430 Type: "object", 5431 }, 5432 Title: "title", 5433 Description: "desc", 5434 MultipleOf: 100, 5435 Maximum: 101, 5436 ExclusiveMaximum: true, 5437 Minimum: 1, 5438 ExclusiveMinimum: true, 5439 MaxLength: 10, 5440 MinLength: 3, 5441 Pattern: "[a-z]+", 5442 MaxItems: 20, 5443 MinItems: 2, 5444 UniqueItems: true, 5445 MaxProperties: 33, 5446 MinProperties: 22, 5447 Required: []string{"req"}, 5448 ReadOnly: true, 5449 }, 5450 }, 5451 }, 5452 { 5453 descr: "JSONSchema with required properties", 5454 msgDescs: []*descriptorpb.DescriptorProto{ 5455 { 5456 Name: proto.String("Message"), 5457 Field: []*descriptorpb.FieldDescriptorProto{ 5458 { 5459 Name: proto.String("FieldOne"), 5460 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5461 Number: proto.Int32(1), 5462 }, 5463 { 5464 Name: proto.String("FieldTwo"), 5465 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5466 Number: proto.Int32(2), 5467 Options: requiredFieldOptions, 5468 }, 5469 { 5470 Name: proto.String("FieldThree"), 5471 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5472 Number: proto.Int32(3), 5473 Options: requiredFieldOptions, 5474 }, 5475 }, 5476 }, 5477 }, 5478 schema: map[string]*openapi_options.Schema{ 5479 "Message": { 5480 JsonSchema: &openapi_options.JSONSchema{ 5481 Title: "title", 5482 Description: "desc", 5483 Required: []string{"FieldOne", "FieldTwo"}, 5484 }, 5485 }, 5486 }, 5487 defs: map[string]openapiSchemaObject{ 5488 "Message": { 5489 schemaCore: schemaCore{ 5490 Type: "object", 5491 }, 5492 Title: "title", 5493 Description: "desc", 5494 Required: []string{"FieldOne", "FieldTwo", "FieldThree"}, 5495 Properties: &openapiSchemaObjectProperties{ 5496 { 5497 Key: "FieldOne", 5498 Value: openapiSchemaObject{ 5499 schemaCore: schemaCore{ 5500 Type: "string", 5501 }, 5502 }, 5503 }, 5504 { 5505 Key: "FieldTwo", 5506 Value: openapiSchemaObject{ 5507 schemaCore: schemaCore{ 5508 Type: "string", 5509 }, 5510 }, 5511 }, 5512 { 5513 Key: "FieldThree", 5514 Value: openapiSchemaObject{ 5515 schemaCore: schemaCore{ 5516 Type: "string", 5517 }, 5518 }, 5519 }, 5520 }, 5521 }, 5522 }, 5523 }, 5524 { 5525 descr: "JSONSchema with required properties", 5526 msgDescs: []*descriptorpb.DescriptorProto{ 5527 { 5528 Name: proto.String("Message"), 5529 Field: []*descriptorpb.FieldDescriptorProto{ 5530 { 5531 Name: proto.String("FieldOne"), 5532 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5533 Number: proto.Int32(3), 5534 Options: requiredFieldOptions, 5535 }, 5536 }, 5537 }, 5538 }, 5539 schema: map[string]*openapi_options.Schema{ 5540 "Message": { 5541 JsonSchema: &openapi_options.JSONSchema{ 5542 Title: "title", 5543 Description: "desc", 5544 }, 5545 }, 5546 }, 5547 defs: map[string]openapiSchemaObject{ 5548 "Message": { 5549 schemaCore: schemaCore{ 5550 Type: "object", 5551 }, 5552 Title: "title", 5553 Description: "desc", 5554 Required: []string{"FieldOne"}, 5555 Properties: &openapiSchemaObjectProperties{ 5556 { 5557 Key: "FieldOne", 5558 Value: openapiSchemaObject{ 5559 schemaCore: schemaCore{ 5560 Type: "string", 5561 }, 5562 }, 5563 }, 5564 }, 5565 }, 5566 }, 5567 }, 5568 { 5569 descr: "JSONSchema with required properties by using annotations", 5570 msgDescs: []*descriptorpb.DescriptorProto{ 5571 { 5572 Name: proto.String("Message"), 5573 Field: []*descriptorpb.FieldDescriptorProto{ 5574 { 5575 Name: proto.String("FieldOne"), 5576 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5577 Number: proto.Int32(2), 5578 Options: requiredFieldOptions, 5579 }, 5580 }, 5581 }, 5582 }, 5583 schema: map[string]*openapi_options.Schema{ 5584 "Message": { 5585 JsonSchema: &openapi_options.JSONSchema{ 5586 Title: "title", 5587 Description: "desc", 5588 }, 5589 }, 5590 }, 5591 defs: map[string]openapiSchemaObject{ 5592 "Message": { 5593 schemaCore: schemaCore{ 5594 Type: "object", 5595 }, 5596 Title: "title", 5597 Description: "desc", 5598 Required: []string{"FieldOne"}, 5599 Properties: &openapiSchemaObjectProperties{ 5600 { 5601 Key: "FieldOne", 5602 Value: openapiSchemaObject{ 5603 schemaCore: schemaCore{ 5604 Type: "string", 5605 }, 5606 }, 5607 }, 5608 }, 5609 }, 5610 }, 5611 }, 5612 { 5613 descr: "JSONSchema with hidden properties", 5614 msgDescs: []*descriptorpb.DescriptorProto{ 5615 { 5616 Name: proto.String("Message"), 5617 Field: []*descriptorpb.FieldDescriptorProto{ 5618 { 5619 Name: proto.String("aInternalField"), 5620 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5621 Number: proto.Int32(1), 5622 Options: fieldVisibilityInternalOption, 5623 }, 5624 { 5625 Name: proto.String("aPreviewField"), 5626 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5627 Number: proto.Int32(2), 5628 Options: fieldVisibilityPreviewOption, 5629 }, 5630 { 5631 Name: proto.String("aVisibleField"), 5632 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5633 Number: proto.Int32(3), 5634 }, 5635 }, 5636 }, 5637 }, 5638 schema: map[string]*openapi_options.Schema{ 5639 "Message": { 5640 JsonSchema: &openapi_options.JSONSchema{ 5641 Title: "title", 5642 Description: "desc", 5643 Required: []string{"req"}, 5644 }, 5645 }, 5646 }, 5647 defs: map[string]openapiSchemaObject{ 5648 "Message": { 5649 schemaCore: schemaCore{ 5650 Type: "object", 5651 }, 5652 Title: "title", 5653 Description: "desc", 5654 Required: []string{"req"}, 5655 Properties: &openapiSchemaObjectProperties{ 5656 { 5657 Key: "aPreviewField", 5658 Value: openapiSchemaObject{ 5659 schemaCore: schemaCore{ 5660 Type: "string", 5661 }, 5662 }, 5663 }, 5664 { 5665 Key: "aVisibleField", 5666 Value: openapiSchemaObject{ 5667 schemaCore: schemaCore{ 5668 Type: "string", 5669 }, 5670 }, 5671 }, 5672 }, 5673 }, 5674 }, 5675 }, 5676 { 5677 descr: "JSONSchema with path parameters", 5678 msgDescs: []*descriptorpb.DescriptorProto{ 5679 { 5680 Name: proto.String("Message"), 5681 Field: []*descriptorpb.FieldDescriptorProto{ 5682 { 5683 Name: proto.String("aRequiredField"), 5684 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5685 Number: proto.Int32(1), 5686 Options: requiredField, 5687 }, 5688 { 5689 Name: proto.String("aPathParameter"), 5690 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5691 Number: proto.Int32(2), 5692 }, 5693 }, 5694 }, 5695 }, 5696 schema: map[string]*openapi_options.Schema{ 5697 "Message": { 5698 JsonSchema: &openapi_options.JSONSchema{ 5699 Title: "title", 5700 Description: "desc", 5701 Required: []string{"req"}, 5702 }, 5703 }, 5704 }, 5705 defs: map[string]openapiSchemaObject{ 5706 "Message": { 5707 schemaCore: schemaCore{ 5708 Type: "object", 5709 }, 5710 Title: "title", 5711 Description: "desc", 5712 Required: []string{"req", "aRequiredField"}, 5713 Properties: &openapiSchemaObjectProperties{ 5714 { 5715 Key: "aRequiredField", 5716 Value: openapiSchemaObject{ 5717 schemaCore: schemaCore{ 5718 Type: "string", 5719 }, 5720 Description: "field description", 5721 Title: "field title", 5722 }, 5723 }, 5724 }, 5725 }, 5726 }, 5727 pathParams: []descriptor.Parameter{ 5728 { 5729 FieldPath: descriptor.FieldPath{ 5730 descriptor.FieldPathComponent{ 5731 Name: ("aPathParameter"), 5732 }, 5733 }, 5734 }, 5735 }, 5736 }, 5737 { 5738 descr: "JSONSchema with required properties via field_behavior", 5739 msgDescs: []*descriptorpb.DescriptorProto{ 5740 { 5741 Name: proto.String("Message"), 5742 Field: []*descriptorpb.FieldDescriptorProto{ 5743 { 5744 Name: proto.String("aRequiredField"), 5745 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5746 Number: proto.Int32(1), 5747 Options: requiredFieldOptions, 5748 }, 5749 { 5750 Name: proto.String("aOutputOnlyField"), 5751 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5752 Number: proto.Int32(2), 5753 Options: fieldBehaviorOutputOnlyOptions, 5754 }, 5755 }, 5756 }, 5757 }, 5758 schema: map[string]*openapi_options.Schema{ 5759 "Message": { 5760 JsonSchema: &openapi_options.JSONSchema{ 5761 Title: "title", 5762 Description: "desc", 5763 Required: []string{"req"}, 5764 }, 5765 }, 5766 }, 5767 defs: map[string]openapiSchemaObject{ 5768 "Message": { 5769 schemaCore: schemaCore{ 5770 Type: "object", 5771 }, 5772 Title: "title", 5773 Description: "desc", 5774 Required: []string{"req", "aRequiredField"}, 5775 Properties: &openapiSchemaObjectProperties{ 5776 { 5777 Key: "aRequiredField", 5778 Value: openapiSchemaObject{ 5779 schemaCore: schemaCore{ 5780 Type: "string", 5781 }, 5782 }, 5783 }, 5784 { 5785 Key: "aOutputOnlyField", 5786 Value: openapiSchemaObject{ 5787 schemaCore: schemaCore{ 5788 Type: "string", 5789 }, 5790 ReadOnly: true, 5791 }, 5792 }, 5793 }, 5794 }, 5795 }, 5796 }, 5797 { 5798 descr: "JSONSchema with required properties and fields with json_name", 5799 msgDescs: []*descriptorpb.DescriptorProto{ 5800 { 5801 Name: proto.String("Message"), 5802 Field: []*descriptorpb.FieldDescriptorProto{ 5803 { 5804 Name: proto.String("FieldOne"), 5805 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5806 Number: proto.Int32(1), 5807 JsonName: proto.String("custom_json_1"), 5808 }, 5809 { 5810 Name: proto.String("FieldTwo"), 5811 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5812 Number: proto.Int32(2), 5813 JsonName: proto.String("custom_json_2"), 5814 Options: requiredFieldOptions, 5815 }, 5816 { 5817 Name: proto.String("FieldThree"), 5818 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 5819 Number: proto.Int32(3), 5820 JsonName: proto.String("custom_json_3"), 5821 Options: requiredFieldOptions, 5822 }, 5823 }, 5824 }, 5825 }, 5826 schema: map[string]*openapi_options.Schema{ 5827 "Message": { 5828 JsonSchema: &openapi_options.JSONSchema{ 5829 Title: "title", 5830 Description: "desc", 5831 Required: []string{"FieldOne", "FieldTwo"}, 5832 }, 5833 }, 5834 }, 5835 defs: map[string]openapiSchemaObject{ 5836 "Message": { 5837 schemaCore: schemaCore{ 5838 Type: "object", 5839 }, 5840 Title: "title", 5841 Description: "desc", 5842 Required: []string{"custom_json_1", "custom_json_2", "custom_json_3"}, 5843 Properties: &openapiSchemaObjectProperties{ 5844 { 5845 Key: "custom_json_1", 5846 Value: openapiSchemaObject{ 5847 schemaCore: schemaCore{ 5848 Type: "string", 5849 }, 5850 }, 5851 }, 5852 { 5853 Key: "custom_json_2", 5854 Value: openapiSchemaObject{ 5855 schemaCore: schemaCore{ 5856 Type: "string", 5857 }, 5858 }, 5859 }, 5860 { 5861 Key: "custom_json_3", 5862 Value: openapiSchemaObject{ 5863 schemaCore: schemaCore{ 5864 Type: "string", 5865 }, 5866 }, 5867 }, 5868 }, 5869 }, 5870 }, 5871 UseJSONNamesForFields: true, 5872 }, 5873 { 5874 descr: "JSONSchema with a read_only nested field", 5875 msgDescs: []*descriptorpb.DescriptorProto{ 5876 { 5877 Name: proto.String("Message"), 5878 Field: []*descriptorpb.FieldDescriptorProto{ 5879 { 5880 Name: proto.String("nested"), 5881 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 5882 TypeName: proto.String(".example.Message.Nested"), 5883 Number: proto.Int32(1), 5884 Options: fieldBehaviorOutputOnlyOptions, 5885 }, 5886 }, 5887 NestedType: []*descriptorpb.DescriptorProto{{ 5888 Name: proto.String("Nested"), 5889 }}, 5890 }, 5891 }, 5892 UseAllOfForRefs: true, 5893 schema: map[string]*openapi_options.Schema{ 5894 "Message": { 5895 JsonSchema: &openapi_options.JSONSchema{ 5896 Title: "title", 5897 Description: "desc", 5898 Required: []string{}, 5899 }, 5900 }, 5901 }, 5902 openAPIOptions: &openapiconfig.OpenAPIOptions{ 5903 Field: []*openapiconfig.OpenAPIFieldOption{ 5904 { 5905 Field: "example.Message.nested", 5906 Option: &openapi_options.JSONSchema{ 5907 Title: "nested field title", 5908 Description: "nested field desc", 5909 Example: `"ok":"TRUE"`, 5910 }, 5911 }, 5912 }, 5913 }, 5914 defs: map[string]openapiSchemaObject{ 5915 "exampleMessage": { 5916 schemaCore: schemaCore{ 5917 Type: "object", 5918 }, 5919 Title: "title", 5920 Description: "desc", 5921 Required: nil, 5922 Properties: &openapiSchemaObjectProperties{ 5923 { 5924 Key: "nested", 5925 Value: openapiSchemaObject{ 5926 AllOf: []allOfEntry{{Ref: "#/definitions/MessageNested"}}, 5927 ReadOnly: true, 5928 schemaCore: schemaCore{ 5929 Example: RawExample(`"ok":"TRUE"`), 5930 }, 5931 Title: "nested field title", 5932 Description: "nested field desc", 5933 }, 5934 }, 5935 }, 5936 }, 5937 }, 5938 }, 5939 } 5940 5941 for _, test := range tests { 5942 t.Run(test.descr, func(t *testing.T) { 5943 5944 msgs := []*descriptor.Message{} 5945 for _, msgdesc := range test.msgDescs { 5946 msgdesc.Options = &descriptorpb.MessageOptions{} 5947 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 5948 } 5949 5950 reg := descriptor.NewRegistry() 5951 file := descriptor.File{ 5952 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 5953 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 5954 Name: proto.String("example.proto"), 5955 Package: proto.String("example"), 5956 Dependency: []string{}, 5957 MessageType: test.msgDescs, 5958 EnumType: []*descriptorpb.EnumDescriptorProto{}, 5959 Service: []*descriptorpb.ServiceDescriptorProto{}, 5960 Options: &descriptorpb.FileOptions{ 5961 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 5962 }, 5963 }, 5964 Messages: msgs, 5965 } 5966 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 5967 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 5968 }) 5969 reg.SetVisibilityRestrictionSelectors([]string{"PREVIEW"}) 5970 5971 if test.UseJSONNamesForFields { 5972 reg.SetUseJSONNamesForFields(true) 5973 } 5974 5975 if test.UseAllOfForRefs { 5976 reg.SetUseAllOfForRefs(true) 5977 } 5978 5979 if err != nil { 5980 t.Fatalf("failed to load code generator request: %v", err) 5981 } 5982 5983 msgMap := map[string]*descriptor.Message{} 5984 for _, d := range test.msgDescs { 5985 name := d.GetName() 5986 msg, err := reg.LookupMsg("example", name) 5987 if err != nil { 5988 t.Fatalf("lookup message %v: %v", name, err) 5989 } 5990 msgMap[msg.FQMN()] = msg 5991 5992 if schema, ok := test.schema[name]; ok { 5993 proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, schema) 5994 } 5995 } 5996 5997 if test.openAPIOptions != nil { 5998 if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil { 5999 t.Fatalf("failed to register OpenAPI options: %s", err) 6000 } 6001 } 6002 6003 refs := make(refMap) 6004 actual := make(openapiDefinitionsObject) 6005 if err := renderMessagesAsDefinition(msgMap, actual, reg, refs, test.pathParams); err != nil { 6006 t.Errorf("renderMessagesAsDefinition failed with: %s", err) 6007 } 6008 6009 if !reflect.DeepEqual(actual, test.defs) { 6010 t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual) 6011 } 6012 }) 6013 } 6014 } 6015 6016 func TestUpdateOpenAPIDataFromComments(t *testing.T) { 6017 6018 tests := []struct { 6019 descr string 6020 openapiSwaggerObject interface{} 6021 comments string 6022 expectedError error 6023 expectedOpenAPIObject interface{} 6024 useGoTemplate bool 6025 goTemplateArgs []string 6026 }{ 6027 { 6028 descr: "empty comments", 6029 openapiSwaggerObject: nil, 6030 expectedOpenAPIObject: nil, 6031 comments: "", 6032 expectedError: nil, 6033 }, 6034 { 6035 descr: "set field to read only", 6036 openapiSwaggerObject: &openapiSchemaObject{}, 6037 expectedOpenAPIObject: &openapiSchemaObject{ 6038 ReadOnly: true, 6039 Description: "... Output only. ...", 6040 }, 6041 comments: "... Output only. ...", 6042 expectedError: nil, 6043 }, 6044 { 6045 descr: "set title", 6046 openapiSwaggerObject: &openapiSchemaObject{}, 6047 expectedOpenAPIObject: &openapiSchemaObject{ 6048 Title: "Comment with no trailing dot", 6049 }, 6050 comments: "Comment with no trailing dot", 6051 expectedError: nil, 6052 }, 6053 { 6054 descr: "set description", 6055 openapiSwaggerObject: &openapiSchemaObject{}, 6056 expectedOpenAPIObject: &openapiSchemaObject{ 6057 Description: "Comment with trailing dot.", 6058 }, 6059 comments: "Comment with trailing dot.", 6060 expectedError: nil, 6061 }, 6062 { 6063 descr: "use info object", 6064 openapiSwaggerObject: &openapiSwaggerObject{ 6065 Info: openapiInfoObject{}, 6066 }, 6067 expectedOpenAPIObject: &openapiSwaggerObject{ 6068 Info: openapiInfoObject{ 6069 Description: "Comment with trailing dot.", 6070 }, 6071 }, 6072 comments: "Comment with trailing dot.", 6073 expectedError: nil, 6074 }, 6075 { 6076 descr: "multi line comment with title", 6077 openapiSwaggerObject: &openapiSchemaObject{}, 6078 expectedOpenAPIObject: &openapiSchemaObject{ 6079 Title: "First line", 6080 Description: "Second line", 6081 }, 6082 comments: "First line\n\nSecond line", 6083 expectedError: nil, 6084 }, 6085 { 6086 descr: "multi line comment no title", 6087 openapiSwaggerObject: &openapiSchemaObject{}, 6088 expectedOpenAPIObject: &openapiSchemaObject{ 6089 Description: "First line.\n\nSecond line", 6090 }, 6091 comments: "First line.\n\nSecond line", 6092 expectedError: nil, 6093 }, 6094 { 6095 descr: "multi line comment with summary with dot", 6096 openapiSwaggerObject: &openapiOperationObject{}, 6097 expectedOpenAPIObject: &openapiOperationObject{ 6098 Summary: "First line.", 6099 Description: "Second line", 6100 }, 6101 comments: "First line.\n\nSecond line", 6102 expectedError: nil, 6103 }, 6104 { 6105 descr: "multi line comment with summary no dot", 6106 openapiSwaggerObject: &openapiOperationObject{}, 6107 expectedOpenAPIObject: &openapiOperationObject{ 6108 Summary: "First line", 6109 Description: "Second line", 6110 }, 6111 comments: "First line\n\nSecond line", 6112 expectedError: nil, 6113 }, 6114 { 6115 descr: "multi line comment with summary no dot", 6116 openapiSwaggerObject: &schemaCore{}, 6117 expectedOpenAPIObject: &schemaCore{}, 6118 comments: "Any comment", 6119 expectedError: errors.New("no description nor summary property"), 6120 }, 6121 { 6122 descr: "without use_go_template", 6123 openapiSwaggerObject: &openapiSchemaObject{}, 6124 expectedOpenAPIObject: &openapiSchemaObject{ 6125 Title: "First line", 6126 Description: "{{import \"documentation.md\"}}", 6127 }, 6128 comments: "First line\n\n{{import \"documentation.md\"}}", 6129 expectedError: nil, 6130 }, 6131 { 6132 descr: "error with use_go_template", 6133 openapiSwaggerObject: &openapiSchemaObject{}, 6134 expectedOpenAPIObject: &openapiSchemaObject{ 6135 Title: "First line", 6136 Description: "open noneexistingfile.txt: no such file or directory", 6137 }, 6138 comments: "First line\n\n{{import \"noneexistingfile.txt\"}}", 6139 expectedError: nil, 6140 useGoTemplate: true, 6141 }, 6142 { 6143 descr: "template with use_go_template", 6144 openapiSwaggerObject: &openapiSchemaObject{}, 6145 expectedOpenAPIObject: &openapiSchemaObject{ 6146 Title: "Template", 6147 Description: `Description "which means nothing"`, 6148 }, 6149 comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6150 expectedError: nil, 6151 useGoTemplate: true, 6152 }, 6153 { 6154 descr: "template with use_go_template and go_template_args", 6155 openapiSwaggerObject: &openapiSchemaObject{}, 6156 expectedOpenAPIObject: &openapiSchemaObject{ 6157 Title: "Template", 6158 Description: `Description "which means nothing" for environment test with value my_value`, 6159 }, 6160 comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} for " + 6161 "environment {{arg \"environment\"}} with value {{arg \"my_key\"}}", 6162 expectedError: nil, 6163 useGoTemplate: true, 6164 goTemplateArgs: []string{"my_key=my_value", "environment=test"}, 6165 }, 6166 { 6167 descr: "template with use_go_template and undefined go_template_args", 6168 openapiSwaggerObject: &openapiSchemaObject{}, 6169 expectedOpenAPIObject: &openapiSchemaObject{ 6170 Title: "Template", 6171 Description: `Description "which means nothing" for environment test with value ` + 6172 `goTemplateArg something_undefined not found`, 6173 }, 6174 comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} for " + 6175 "environment {{arg \"environment\"}} with value {{arg \"something_undefined\"}}", 6176 expectedError: nil, 6177 useGoTemplate: true, 6178 goTemplateArgs: []string{"environment=test"}, 6179 }, 6180 } 6181 6182 for _, test := range tests { 6183 t.Run(test.descr, func(t *testing.T) { 6184 reg := descriptor.NewRegistry() 6185 if test.useGoTemplate { 6186 reg.SetUseGoTemplate(true) 6187 } 6188 if len(test.goTemplateArgs) > 0 { 6189 reg.SetGoTemplateArgs(test.goTemplateArgs) 6190 } 6191 err := updateOpenAPIDataFromComments(reg, test.openapiSwaggerObject, nil, test.comments, false) 6192 if test.expectedError == nil { 6193 if err != nil { 6194 t.Errorf("unexpected error '%v'", err) 6195 } 6196 if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) { 6197 t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject) 6198 } 6199 } else { 6200 if err == nil { 6201 t.Error("expected update error not returned") 6202 } 6203 if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) { 6204 t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject) 6205 } 6206 if err.Error() != test.expectedError.Error() { 6207 t.Errorf("expected error malformed, expected %q, got %q", test.expectedError.Error(), err.Error()) 6208 } 6209 } 6210 }) 6211 } 6212 } 6213 6214 func TestMessageOptionsWithGoTemplate(t *testing.T) { 6215 tests := []struct { 6216 descr string 6217 msgDescs []*descriptorpb.DescriptorProto 6218 schema map[string]*openapi_options.Schema // per-message schema to add 6219 defs openapiDefinitionsObject 6220 openAPIOptions *openapiconfig.OpenAPIOptions 6221 useGoTemplate bool 6222 goTemplateArgs []string 6223 }{ 6224 { 6225 descr: "external docs option", 6226 msgDescs: []*descriptorpb.DescriptorProto{ 6227 {Name: proto.String("Message")}, 6228 }, 6229 schema: map[string]*openapi_options.Schema{ 6230 "Message": { 6231 JsonSchema: &openapi_options.JSONSchema{ 6232 Title: "{{.Name}}", 6233 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6234 }, 6235 ExternalDocs: &openapi_options.ExternalDocumentation{ 6236 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6237 }, 6238 }, 6239 }, 6240 defs: map[string]openapiSchemaObject{ 6241 "Message": { 6242 schemaCore: schemaCore{ 6243 Type: "object", 6244 }, 6245 Title: "Message", 6246 Description: `Description "which means nothing"`, 6247 ExternalDocs: &openapiExternalDocumentationObject{ 6248 Description: `Description "which means nothing"`, 6249 }, 6250 }, 6251 }, 6252 useGoTemplate: true, 6253 }, 6254 { 6255 descr: "external docs option", 6256 msgDescs: []*descriptorpb.DescriptorProto{ 6257 {Name: proto.String("Message")}, 6258 }, 6259 schema: map[string]*openapi_options.Schema{ 6260 "Message": { 6261 JsonSchema: &openapi_options.JSONSchema{ 6262 Title: "{{.Name}}", 6263 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6264 }, 6265 ExternalDocs: &openapi_options.ExternalDocumentation{ 6266 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6267 }, 6268 }, 6269 }, 6270 defs: map[string]openapiSchemaObject{ 6271 "Message": { 6272 schemaCore: schemaCore{ 6273 Type: "object", 6274 }, 6275 Title: "{{.Name}}", 6276 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6277 ExternalDocs: &openapiExternalDocumentationObject{ 6278 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6279 }, 6280 }, 6281 }, 6282 useGoTemplate: false, 6283 }, 6284 { 6285 descr: "external docs option with go template args", 6286 msgDescs: []*descriptorpb.DescriptorProto{ 6287 {Name: proto.String("Message")}, 6288 }, 6289 schema: map[string]*openapi_options.Schema{ 6290 "Message": { 6291 JsonSchema: &openapi_options.JSONSchema{ 6292 Title: "{{.Name}}", 6293 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " + 6294 "{{arg \"my_key\"}}", 6295 }, 6296 ExternalDocs: &openapi_options.ExternalDocumentation{ 6297 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " + 6298 "{{arg \"my_key\"}}", 6299 }, 6300 }, 6301 }, 6302 defs: map[string]openapiSchemaObject{ 6303 "Message": { 6304 schemaCore: schemaCore{ 6305 Type: "object", 6306 }, 6307 Title: "Message", 6308 Description: `Description "which means nothing" too`, 6309 ExternalDocs: &openapiExternalDocumentationObject{ 6310 Description: `Description "which means nothing" too`, 6311 }, 6312 }, 6313 }, 6314 useGoTemplate: true, 6315 goTemplateArgs: []string{"my_key=too"}, 6316 }, 6317 { 6318 descr: "registered OpenAPIOption", 6319 msgDescs: []*descriptorpb.DescriptorProto{ 6320 {Name: proto.String("Message")}, 6321 }, 6322 openAPIOptions: &openapiconfig.OpenAPIOptions{ 6323 Message: []*openapiconfig.OpenAPIMessageOption{ 6324 { 6325 Message: "example.Message", 6326 Option: &openapi_options.Schema{ 6327 JsonSchema: &openapi_options.JSONSchema{ 6328 Title: "{{.Name}}", 6329 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6330 }, 6331 ExternalDocs: &openapi_options.ExternalDocumentation{ 6332 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}", 6333 }, 6334 }, 6335 }, 6336 }, 6337 }, 6338 defs: map[string]openapiSchemaObject{ 6339 "Message": { 6340 schemaCore: schemaCore{ 6341 Type: "object", 6342 }, 6343 Title: "Message", 6344 Description: `Description "which means nothing"`, 6345 ExternalDocs: &openapiExternalDocumentationObject{ 6346 Description: `Description "which means nothing"`, 6347 }, 6348 }, 6349 }, 6350 useGoTemplate: true, 6351 }, 6352 { 6353 descr: "registered OpenAPIOption with go template args", 6354 msgDescs: []*descriptorpb.DescriptorProto{ 6355 {Name: proto.String("Message")}, 6356 }, 6357 openAPIOptions: &openapiconfig.OpenAPIOptions{ 6358 Message: []*openapiconfig.OpenAPIMessageOption{ 6359 { 6360 Message: "example.Message", 6361 Option: &openapi_options.Schema{ 6362 JsonSchema: &openapi_options.JSONSchema{ 6363 Title: "{{.Name}}", 6364 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " + 6365 "{{arg \"my_key\"}}", 6366 }, 6367 ExternalDocs: &openapi_options.ExternalDocumentation{ 6368 Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " + 6369 "{{arg \"my_key\"}}", 6370 }, 6371 }, 6372 }, 6373 }, 6374 }, 6375 defs: map[string]openapiSchemaObject{ 6376 "Message": { 6377 schemaCore: schemaCore{ 6378 Type: "object", 6379 }, 6380 Title: "Message", 6381 Description: `Description "which means nothing" too`, 6382 ExternalDocs: &openapiExternalDocumentationObject{ 6383 Description: `Description "which means nothing" too`, 6384 }, 6385 }, 6386 }, 6387 useGoTemplate: true, 6388 goTemplateArgs: []string{"my_key=too"}, 6389 }, 6390 } 6391 6392 for _, test := range tests { 6393 t.Run(test.descr, func(t *testing.T) { 6394 6395 msgs := []*descriptor.Message{} 6396 for _, msgdesc := range test.msgDescs { 6397 msgdesc.Options = &descriptorpb.MessageOptions{} 6398 msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc}) 6399 } 6400 6401 reg := descriptor.NewRegistry() 6402 reg.SetUseGoTemplate(test.useGoTemplate) 6403 reg.SetGoTemplateArgs(test.goTemplateArgs) 6404 file := descriptor.File{ 6405 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 6406 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 6407 Name: proto.String("example.proto"), 6408 Package: proto.String("example"), 6409 Dependency: []string{}, 6410 MessageType: test.msgDescs, 6411 EnumType: []*descriptorpb.EnumDescriptorProto{}, 6412 Service: []*descriptorpb.ServiceDescriptorProto{}, 6413 Options: &descriptorpb.FileOptions{ 6414 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 6415 }, 6416 }, 6417 Messages: msgs, 6418 } 6419 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 6420 ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}, 6421 }) 6422 if err != nil { 6423 t.Fatalf("failed to load code generator request: %v", err) 6424 } 6425 6426 msgMap := map[string]*descriptor.Message{} 6427 for _, d := range test.msgDescs { 6428 name := d.GetName() 6429 msg, err := reg.LookupMsg("example", name) 6430 if err != nil { 6431 t.Fatalf("lookup message %v: %v", name, err) 6432 } 6433 msgMap[msg.FQMN()] = msg 6434 6435 if schema, ok := test.schema[name]; ok { 6436 proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, schema) 6437 } 6438 } 6439 6440 if test.openAPIOptions != nil { 6441 if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil { 6442 t.Fatalf("failed to register OpenAPI options: %s", err) 6443 } 6444 } 6445 6446 refs := make(refMap) 6447 actual := make(openapiDefinitionsObject) 6448 if err := renderMessagesAsDefinition(msgMap, actual, reg, refs, nil); err != nil { 6449 t.Errorf("renderMessagesAsDefinition failed with: %s", err) 6450 } 6451 6452 if !reflect.DeepEqual(actual, test.defs) { 6453 t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual) 6454 } 6455 }) 6456 } 6457 } 6458 6459 func TestTagsWithGoTemplate(t *testing.T) { 6460 reg := descriptor.NewRegistry() 6461 reg.SetUseGoTemplate(true) 6462 reg.SetGoTemplateArgs([]string{"my_key=my_value"}) 6463 6464 svc := &descriptorpb.ServiceDescriptorProto{ 6465 Name: proto.String("ExampleService"), 6466 Options: &descriptorpb.ServiceOptions{}, 6467 } 6468 6469 file := descriptor.File{ 6470 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 6471 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 6472 Name: proto.String("example.proto"), 6473 Package: proto.String("example"), 6474 Dependency: []string{}, 6475 MessageType: []*descriptorpb.DescriptorProto{}, 6476 EnumType: []*descriptorpb.EnumDescriptorProto{}, 6477 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 6478 Options: &descriptorpb.FileOptions{ 6479 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 6480 }, 6481 }, 6482 Messages: []*descriptor.Message{}, 6483 Services: []*descriptor.Service{ 6484 { 6485 ServiceDescriptorProto: svc, 6486 }, 6487 }, 6488 } 6489 6490 // Set tag through service extension 6491 proto.SetExtension(file.GetService()[0].Options, openapi_options.E_Openapiv2Tag, &openapi_options.Tag{ 6492 Name: "service tag", 6493 Description: "{{ .Name }}!"}) 6494 6495 // Set tags through file extension 6496 swagger := openapi_options.Swagger{ 6497 Tags: []*openapi_options.Tag{ 6498 { 6499 Name: "not a service tag", 6500 Description: "{{ import \"file\" }}", 6501 }, 6502 { 6503 Name: "ExampleService", 6504 Description: "ExampleService!", 6505 }, 6506 { 6507 Name: "not a service tag 2", 6508 Description: "{{ import \"file\" }}", 6509 }, 6510 { 6511 Name: "Service with my_key", 6512 Description: "the {{arg \"my_key\"}}", 6513 }, 6514 }, 6515 } 6516 proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger) 6517 6518 actual, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 6519 if err != nil { 6520 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 6521 } 6522 expectedTags := []openapiTagObject{ 6523 { 6524 Name: "not a service tag", 6525 Description: "open file: no such file or directory", 6526 }, 6527 { 6528 Name: "ExampleService", 6529 Description: "ExampleService!", 6530 }, 6531 { 6532 Name: "not a service tag 2", 6533 Description: "open file: no such file or directory", 6534 }, 6535 { 6536 Name: "Service with my_key", 6537 Description: "the my_value", 6538 }, 6539 { 6540 Name: "service tag", 6541 Description: "ExampleService!", 6542 }, 6543 } 6544 if !reflect.DeepEqual(actual.Tags, expectedTags) { 6545 t.Errorf("Expected tags %+v, not %+v", expectedTags, actual.Tags) 6546 } 6547 } 6548 6549 func TestTemplateWithoutErrorDefinition(t *testing.T) { 6550 msgdesc := &descriptorpb.DescriptorProto{ 6551 Name: proto.String("ExampleMessage"), 6552 Field: []*descriptorpb.FieldDescriptorProto{}, 6553 } 6554 meth := &descriptorpb.MethodDescriptorProto{ 6555 Name: proto.String("Echo"), 6556 InputType: proto.String("ExampleMessage"), 6557 OutputType: proto.String("ExampleMessage"), 6558 } 6559 svc := &descriptorpb.ServiceDescriptorProto{ 6560 Name: proto.String("ExampleService"), 6561 Method: []*descriptorpb.MethodDescriptorProto{meth}, 6562 } 6563 6564 msg := &descriptor.Message{ 6565 DescriptorProto: msgdesc, 6566 } 6567 6568 file := descriptor.File{ 6569 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 6570 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 6571 Name: proto.String("example.proto"), 6572 Package: proto.String("example"), 6573 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 6574 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 6575 Options: &descriptorpb.FileOptions{ 6576 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 6577 }, 6578 }, 6579 GoPkg: descriptor.GoPackage{ 6580 Path: "example.com/path/to/example/example.pb", 6581 Name: "example_pb", 6582 }, 6583 Messages: []*descriptor.Message{msg}, 6584 Services: []*descriptor.Service{ 6585 { 6586 ServiceDescriptorProto: svc, 6587 Methods: []*descriptor.Method{ 6588 { 6589 MethodDescriptorProto: meth, 6590 RequestType: msg, 6591 ResponseType: msg, 6592 Bindings: []*descriptor.Binding{ 6593 { 6594 HTTPMethod: "POST", 6595 PathTmpl: httprule.Template{ 6596 Version: 1, 6597 OpCodes: []int{0, 0}, 6598 Template: "/v1/echo", 6599 }, 6600 Body: &descriptor.Body{ 6601 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 6602 }, 6603 }, 6604 }, 6605 }, 6606 }, 6607 }, 6608 }, 6609 } 6610 reg := descriptor.NewRegistry() 6611 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 6612 if err != nil { 6613 t.Errorf("failed to reg.Load(): %v", err) 6614 return 6615 } 6616 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 6617 if err != nil { 6618 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 6619 return 6620 } 6621 6622 defRsp, ok := result.getPathItemObject("/v1/echo").Post.Responses["default"] 6623 if !ok { 6624 return 6625 } 6626 6627 ref := defRsp.Schema.schemaCore.Ref 6628 refName := strings.TrimPrefix(ref, "#/definitions/") 6629 if refName == "" { 6630 t.Fatal("created default Error response with empty reflink") 6631 } 6632 6633 if _, ok := result.Definitions[refName]; !ok { 6634 t.Errorf("default Error response with reflink '%v', but its definition was not found", refName) 6635 } 6636 } 6637 6638 func TestSingleServiceTemplateWithDuplicateHttp1Operations(t *testing.T) { 6639 fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING 6640 field1 := &descriptorpb.FieldDescriptorProto{ 6641 Name: proto.String("name"), 6642 Number: proto.Int32(1), 6643 Type: &fieldType, 6644 } 6645 6646 getFooMsgDesc := &descriptorpb.DescriptorProto{ 6647 Name: proto.String("GetFooRequest"), 6648 Field: []*descriptorpb.FieldDescriptorProto{ 6649 field1, 6650 }, 6651 } 6652 getFooMsg := &descriptor.Message{ 6653 DescriptorProto: getFooMsgDesc, 6654 } 6655 deleteFooMsgDesc := &descriptorpb.DescriptorProto{ 6656 Name: proto.String("DeleteFooRequest"), 6657 Field: []*descriptorpb.FieldDescriptorProto{ 6658 field1, 6659 }, 6660 } 6661 deleteFooMsg := &descriptor.Message{ 6662 DescriptorProto: deleteFooMsgDesc, 6663 } 6664 getFoo := &descriptorpb.MethodDescriptorProto{ 6665 Name: proto.String("GetFoo"), 6666 InputType: proto.String("GetFooRequest"), 6667 OutputType: proto.String("EmptyMessage"), 6668 } 6669 deleteFoo := &descriptorpb.MethodDescriptorProto{ 6670 Name: proto.String("DeleteFoo"), 6671 InputType: proto.String("DeleteFooRequest"), 6672 OutputType: proto.String("EmptyMessage"), 6673 } 6674 6675 getBarMsgDesc := &descriptorpb.DescriptorProto{ 6676 Name: proto.String("GetBarRequest"), 6677 Field: []*descriptorpb.FieldDescriptorProto{ 6678 field1, 6679 }, 6680 } 6681 getBarMsg := &descriptor.Message{ 6682 DescriptorProto: getBarMsgDesc, 6683 } 6684 deleteBarMsgDesc := &descriptorpb.DescriptorProto{ 6685 Name: proto.String("DeleteBarRequest"), 6686 Field: []*descriptorpb.FieldDescriptorProto{ 6687 field1, 6688 }, 6689 } 6690 deleteBarMsg := &descriptor.Message{ 6691 DescriptorProto: deleteBarMsgDesc, 6692 } 6693 getBar := &descriptorpb.MethodDescriptorProto{ 6694 Name: proto.String("GetBar"), 6695 InputType: proto.String("GetBarRequest"), 6696 OutputType: proto.String("EmptyMessage"), 6697 } 6698 deleteBar := &descriptorpb.MethodDescriptorProto{ 6699 Name: proto.String("DeleteBar"), 6700 InputType: proto.String("DeleteBarRequest"), 6701 OutputType: proto.String("EmptyMessage"), 6702 } 6703 6704 svc1 := &descriptorpb.ServiceDescriptorProto{ 6705 Name: proto.String("Service1"), 6706 Method: []*descriptorpb.MethodDescriptorProto{getFoo, deleteFoo, getBar, deleteBar}, 6707 } 6708 6709 emptyMsgDesc := &descriptorpb.DescriptorProto{ 6710 Name: proto.String("EmptyMessage"), 6711 } 6712 emptyMsg := &descriptor.Message{ 6713 DescriptorProto: emptyMsgDesc, 6714 } 6715 6716 file := descriptor.File{ 6717 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 6718 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 6719 Name: proto.String("service1.proto"), 6720 Package: proto.String("example"), 6721 MessageType: []*descriptorpb.DescriptorProto{getBarMsgDesc, deleteBarMsgDesc, getFooMsgDesc, deleteFooMsgDesc, emptyMsgDesc}, 6722 Service: []*descriptorpb.ServiceDescriptorProto{svc1}, 6723 Options: &descriptorpb.FileOptions{ 6724 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 6725 }, 6726 }, 6727 GoPkg: descriptor.GoPackage{ 6728 Path: "example.com/path/to/example/example.pb", 6729 Name: "example_pb", 6730 }, 6731 Messages: []*descriptor.Message{getFooMsg, deleteFooMsg, getBarMsg, deleteBarMsg, emptyMsg}, 6732 Services: []*descriptor.Service{ 6733 { 6734 ServiceDescriptorProto: svc1, 6735 Methods: []*descriptor.Method{ 6736 { 6737 MethodDescriptorProto: getFoo, 6738 RequestType: getFooMsg, 6739 ResponseType: getFooMsg, 6740 Bindings: []*descriptor.Binding{ 6741 { 6742 HTTPMethod: "GET", 6743 PathTmpl: httprule.Template{ 6744 Version: 1, 6745 OpCodes: []int{0, 0}, 6746 Template: "/v1/{name=foos/*}", 6747 }, 6748 PathParams: []descriptor.Parameter{ 6749 { 6750 Target: &descriptor.Field{ 6751 FieldDescriptorProto: field1, 6752 Message: getFooMsg, 6753 }, 6754 FieldPath: descriptor.FieldPath{ 6755 { 6756 Name: "name", 6757 }, 6758 }, 6759 }, 6760 }, 6761 Body: &descriptor.Body{ 6762 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 6763 }, 6764 }, 6765 }, 6766 }, 6767 { 6768 MethodDescriptorProto: deleteFoo, 6769 RequestType: deleteFooMsg, 6770 ResponseType: emptyMsg, 6771 Bindings: []*descriptor.Binding{ 6772 { 6773 HTTPMethod: "DELETE", 6774 PathTmpl: httprule.Template{ 6775 Version: 1, 6776 OpCodes: []int{0, 0}, 6777 Template: "/v1/{name=foos/*}", 6778 }, 6779 PathParams: []descriptor.Parameter{ 6780 { 6781 Target: &descriptor.Field{ 6782 FieldDescriptorProto: field1, 6783 Message: deleteFooMsg, 6784 }, 6785 FieldPath: descriptor.FieldPath{ 6786 { 6787 Name: "name", 6788 }, 6789 }, 6790 }, 6791 }, 6792 Body: &descriptor.Body{ 6793 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 6794 }, 6795 }, 6796 }, 6797 }, 6798 { 6799 MethodDescriptorProto: getBar, 6800 RequestType: getBarMsg, 6801 ResponseType: getBarMsg, 6802 Bindings: []*descriptor.Binding{ 6803 { 6804 HTTPMethod: "GET", 6805 PathTmpl: httprule.Template{ 6806 Version: 1, 6807 OpCodes: []int{0, 0}, 6808 Template: "/v1/{name=bars/*}", 6809 }, 6810 PathParams: []descriptor.Parameter{ 6811 { 6812 Target: &descriptor.Field{ 6813 FieldDescriptorProto: field1, 6814 Message: getBarMsg, 6815 }, 6816 FieldPath: descriptor.FieldPath{ 6817 { 6818 Name: "name", 6819 }, 6820 }, 6821 }, 6822 }, 6823 Body: &descriptor.Body{ 6824 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 6825 }, 6826 }, 6827 }, 6828 }, 6829 { 6830 MethodDescriptorProto: deleteBar, 6831 RequestType: deleteBarMsg, 6832 ResponseType: emptyMsg, 6833 Bindings: []*descriptor.Binding{ 6834 { 6835 HTTPMethod: "DELETE", 6836 PathTmpl: httprule.Template{ 6837 Version: 1, 6838 OpCodes: []int{0, 0}, 6839 Template: "/v1/{name=bars/*}", 6840 }, 6841 PathParams: []descriptor.Parameter{ 6842 { 6843 Target: &descriptor.Field{ 6844 FieldDescriptorProto: field1, 6845 Message: deleteBarMsg, 6846 }, 6847 FieldPath: descriptor.FieldPath{ 6848 { 6849 Name: "name", 6850 }, 6851 }, 6852 }, 6853 }, 6854 Body: &descriptor.Body{ 6855 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 6856 }, 6857 }, 6858 }, 6859 }, 6860 }, 6861 }, 6862 }, 6863 } 6864 reg := descriptor.NewRegistry() 6865 err := reg.Load(reqFromFile(&file)) 6866 if err != nil { 6867 t.Fatalf("failed to reg.Load(): %v", err) 6868 } 6869 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 6870 if err != nil { 6871 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 6872 } 6873 6874 if got, want := len(result.Paths), 2; got != want { 6875 t.Fatalf("Results path length differed, got %d want %d", got, want) 6876 } 6877 6878 firstOpGet := result.getPathItemObject("/v1/{name}").Get 6879 if got, want := firstOpGet.OperationID, "Service1_GetFoo"; got != want { 6880 t.Fatalf("First operation GET id differed, got %s want %s", got, want) 6881 } 6882 if got, want := len(firstOpGet.Parameters), 2; got != want { 6883 t.Fatalf("First operation GET params length differed, got %d want %d", got, want) 6884 } 6885 if got, want := firstOpGet.Parameters[0].Name, "name"; got != want { 6886 t.Fatalf("First operation GET first param name differed, got %s want %s", got, want) 6887 } 6888 if got, want := firstOpGet.Parameters[0].Pattern, "foos/[^/]+"; got != want { 6889 t.Fatalf("First operation GET first param pattern differed, got %s want %s", got, want) 6890 } 6891 if got, want := firstOpGet.Parameters[1].In, "body"; got != want { 6892 t.Fatalf("First operation GET second param 'in' differed, got %s want %s", got, want) 6893 } 6894 6895 firstOpDelete := result.getPathItemObject("/v1/{name}").Delete 6896 if got, want := firstOpDelete.OperationID, "Service1_DeleteFoo"; got != want { 6897 t.Fatalf("First operation id DELETE differed, got %s want %s", got, want) 6898 } 6899 if got, want := len(firstOpDelete.Parameters), 2; got != want { 6900 t.Fatalf("First operation DELETE params length differed, got %d want %d", got, want) 6901 } 6902 if got, want := firstOpDelete.Parameters[0].Name, "name"; got != want { 6903 t.Fatalf("First operation DELETE first param name differed, got %s want %s", got, want) 6904 } 6905 if got, want := firstOpDelete.Parameters[0].Pattern, "foos/[^/]+"; got != want { 6906 t.Fatalf("First operation DELETE first param pattern differed, got %s want %s", got, want) 6907 } 6908 if got, want := firstOpDelete.Parameters[1].In, "body"; got != want { 6909 t.Fatalf("First operation DELETE second param 'in' differed, got %s want %s", got, want) 6910 } 6911 6912 secondOpGet := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}").Get 6913 if got, want := secondOpGet.OperationID, "Service1_GetBar"; got != want { 6914 t.Fatalf("Second operation id GET differed, got %s want %s", got, want) 6915 } 6916 if got, want := len(secondOpGet.Parameters), 2; got != want { 6917 t.Fatalf("Second operation GET params length differed, got %d want %d", got, want) 6918 } 6919 if got, want := secondOpGet.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want { 6920 t.Fatalf("Second operation GET first param name differed, got %s want %s", got, want) 6921 } 6922 if got, want := secondOpGet.Parameters[0].Pattern, "bars/[^/]+"; got != want { 6923 t.Fatalf("Second operation GET first param pattern differed, got %s want %s", got, want) 6924 } 6925 if got, want := secondOpGet.Parameters[1].In, "body"; got != want { 6926 t.Fatalf("Second operation GET second param 'in' differed, got %s want %s", got, want) 6927 } 6928 6929 secondOpDelete := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}").Delete 6930 if got, want := secondOpDelete.OperationID, "Service1_DeleteBar"; got != want { 6931 t.Fatalf("Second operation id differed, got %s want %s", got, want) 6932 } 6933 if got, want := len(secondOpDelete.Parameters), 2; got != want { 6934 t.Fatalf("Second operation params length differed, got %d want %d", got, want) 6935 } 6936 if got, want := secondOpDelete.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want { 6937 t.Fatalf("Second operation first param name differed, got %s want %s", got, want) 6938 } 6939 if got, want := secondOpDelete.Parameters[0].Pattern, "bars/[^/]+"; got != want { 6940 t.Fatalf("Second operation first param pattern differed, got %s want %s", got, want) 6941 } 6942 if got, want := secondOpDelete.Parameters[1].In, "body"; got != want { 6943 t.Fatalf("Second operation third param 'in' differed, got %s want %s", got, want) 6944 } 6945 } 6946 6947 func getOperation(pathItem openapiPathItemObject, httpMethod string) *openapiOperationObject { 6948 switch httpMethod { 6949 case "GET": 6950 return pathItem.Get 6951 case "POST": 6952 return pathItem.Post 6953 case "PUT": 6954 return pathItem.Put 6955 case "DELETE": 6956 return pathItem.Delete 6957 case "PATCH": 6958 return pathItem.Patch 6959 case "HEAD": 6960 return pathItem.Head 6961 case "OPTIONS": 6962 return pathItem.Options 6963 default: 6964 return nil 6965 } 6966 } 6967 6968 func TestSingleServiceTemplateWithDuplicateInAllSupportedHttp1Operations(t *testing.T) { 6969 supportedMethods := []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} 6970 6971 for _, method := range supportedMethods { 6972 fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING 6973 field1 := &descriptorpb.FieldDescriptorProto{ 6974 Name: proto.String("name"), 6975 Number: proto.Int32(1), 6976 Type: &fieldType, 6977 } 6978 6979 methodFooMsgDesc := &descriptorpb.DescriptorProto{ 6980 Name: proto.String(method + "FooRequest"), 6981 Field: []*descriptorpb.FieldDescriptorProto{ 6982 field1, 6983 }, 6984 } 6985 methodFooMsg := &descriptor.Message{ 6986 DescriptorProto: methodFooMsgDesc, 6987 } 6988 methodFoo := &descriptorpb.MethodDescriptorProto{ 6989 Name: proto.String(method + "Foo"), 6990 InputType: proto.String(method + "FooRequest"), 6991 OutputType: proto.String("EmptyMessage"), 6992 } 6993 6994 methodBarMsgDesc := &descriptorpb.DescriptorProto{ 6995 Name: proto.String(method + "BarRequest"), 6996 Field: []*descriptorpb.FieldDescriptorProto{ 6997 field1, 6998 }, 6999 } 7000 methodBarMsg := &descriptor.Message{ 7001 DescriptorProto: methodBarMsgDesc, 7002 } 7003 methodBar := &descriptorpb.MethodDescriptorProto{ 7004 Name: proto.String(method + "Bar"), 7005 InputType: proto.String(method + "BarRequest"), 7006 OutputType: proto.String("EmptyMessage"), 7007 } 7008 7009 svc1 := &descriptorpb.ServiceDescriptorProto{ 7010 Name: proto.String("Service1"), 7011 Method: []*descriptorpb.MethodDescriptorProto{methodFoo, methodBar}, 7012 } 7013 7014 emptyMsgDesc := &descriptorpb.DescriptorProto{ 7015 Name: proto.String("EmptyMessage"), 7016 } 7017 emptyMsg := &descriptor.Message{ 7018 DescriptorProto: emptyMsgDesc, 7019 } 7020 7021 file := descriptor.File{ 7022 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 7023 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 7024 Name: proto.String("service1.proto"), 7025 Package: proto.String("example"), 7026 MessageType: []*descriptorpb.DescriptorProto{methodBarMsgDesc, methodFooMsgDesc, emptyMsgDesc}, 7027 Service: []*descriptorpb.ServiceDescriptorProto{svc1}, 7028 Options: &descriptorpb.FileOptions{ 7029 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 7030 }, 7031 }, 7032 GoPkg: descriptor.GoPackage{ 7033 Path: "example.com/path/to/example/example.pb", 7034 Name: "example_pb", 7035 }, 7036 Messages: []*descriptor.Message{methodFooMsg, methodBarMsg, emptyMsg}, 7037 Services: []*descriptor.Service{ 7038 { 7039 ServiceDescriptorProto: svc1, 7040 Methods: []*descriptor.Method{ 7041 { 7042 MethodDescriptorProto: methodFoo, 7043 RequestType: methodFooMsg, 7044 ResponseType: methodFooMsg, 7045 Bindings: []*descriptor.Binding{ 7046 { 7047 HTTPMethod: method, 7048 PathTmpl: httprule.Template{ 7049 Version: 1, 7050 OpCodes: []int{0, 0}, 7051 Template: "/v1/{name=foos/*}", 7052 }, 7053 PathParams: []descriptor.Parameter{ 7054 { 7055 Target: &descriptor.Field{ 7056 FieldDescriptorProto: field1, 7057 Message: methodFooMsg, 7058 }, 7059 FieldPath: descriptor.FieldPath{ 7060 { 7061 Name: "name", 7062 }, 7063 }, 7064 }, 7065 }, 7066 Body: &descriptor.Body{ 7067 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7068 }, 7069 }, 7070 }, 7071 }, 7072 { 7073 MethodDescriptorProto: methodBar, 7074 RequestType: methodBarMsg, 7075 ResponseType: methodBarMsg, 7076 Bindings: []*descriptor.Binding{ 7077 { 7078 HTTPMethod: method, 7079 PathTmpl: httprule.Template{ 7080 Version: 1, 7081 OpCodes: []int{0, 0}, 7082 Template: "/v1/{name=bars/*}", 7083 }, 7084 PathParams: []descriptor.Parameter{ 7085 { 7086 Target: &descriptor.Field{ 7087 FieldDescriptorProto: field1, 7088 Message: methodBarMsg, 7089 }, 7090 FieldPath: descriptor.FieldPath{ 7091 { 7092 Name: "name", 7093 }, 7094 }, 7095 }, 7096 }, 7097 Body: &descriptor.Body{ 7098 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7099 }, 7100 }, 7101 }, 7102 }, 7103 }, 7104 }, 7105 }, 7106 } 7107 reg := descriptor.NewRegistry() 7108 err := reg.Load(reqFromFile(&file)) 7109 if err != nil { 7110 t.Fatalf("failed to reg.Load(): %v", err) 7111 } 7112 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 7113 if err != nil { 7114 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 7115 } 7116 7117 if got, want := len(result.Paths), 2; got != want { 7118 t.Fatalf("Results path length differed, got %d want %d", got, want) 7119 } 7120 7121 firstOpMethod := getOperation(result.getPathItemObject("/v1/{name}"), method) 7122 if got, want := firstOpMethod.OperationID, "Service1_"+method+"Foo"; got != want { 7123 t.Fatalf("First operation %s id differed, got %s want %s", method, got, want) 7124 } 7125 if got, want := len(firstOpMethod.Parameters), 2; got != want { 7126 t.Fatalf("First operation %s params length differed, got %d want %d", method, got, want) 7127 } 7128 if got, want := firstOpMethod.Parameters[0].Name, "name"; got != want { 7129 t.Fatalf("First operation %s first param name differed, got %s want %s", method, got, want) 7130 } 7131 if got, want := firstOpMethod.Parameters[0].Pattern, "foos/[^/]+"; got != want { 7132 t.Fatalf("First operation %s first param pattern differed, got %s want %s", method, got, want) 7133 } 7134 if got, want := firstOpMethod.Parameters[1].In, "body"; got != want { 7135 t.Fatalf("First operation %s second param 'in' differed, got %s want %s", method, got, want) 7136 } 7137 7138 secondOpMethod := getOperation(result.getPathItemObject("/v1/{name"+pathParamUniqueSuffixDeliminator+"1}"), method) 7139 if got, want := secondOpMethod.OperationID, "Service1_"+method+"Bar"; got != want { 7140 t.Fatalf("Second operation id %s differed, got %s want %s", method, got, want) 7141 } 7142 if got, want := len(secondOpMethod.Parameters), 2; got != want { 7143 t.Fatalf("Second operation %s params length differed, got %d want %d", method, got, want) 7144 } 7145 if got, want := secondOpMethod.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want { 7146 t.Fatalf("Second operation %s first param name differed, got %s want %s", method, got, want) 7147 } 7148 if got, want := secondOpMethod.Parameters[0].Pattern, "bars/[^/]+"; got != want { 7149 t.Fatalf("Second operation %s first param pattern differed, got %s want %s", method, got, want) 7150 } 7151 if got, want := secondOpMethod.Parameters[1].In, "body"; got != want { 7152 t.Fatalf("Second operation %s second param 'in' differed, got %s want %s", method, got, want) 7153 } 7154 } 7155 } 7156 7157 func TestSingleServiceTemplateWithDuplicateHttp1UnsupportedOperations(t *testing.T) { 7158 fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING 7159 field1 := &descriptorpb.FieldDescriptorProto{ 7160 Name: proto.String("name"), 7161 Number: proto.Int32(1), 7162 Type: &fieldType, 7163 } 7164 7165 unsupportedFooMsgDesc := &descriptorpb.DescriptorProto{ 7166 Name: proto.String("UnsupportedFooRequest"), 7167 Field: []*descriptorpb.FieldDescriptorProto{ 7168 field1, 7169 }, 7170 } 7171 unsupportedFooMsg := &descriptor.Message{ 7172 DescriptorProto: unsupportedFooMsgDesc, 7173 } 7174 unsupportedFoo := &descriptorpb.MethodDescriptorProto{ 7175 Name: proto.String("UnsupportedFoo"), 7176 InputType: proto.String("UnsupportedFooRequest"), 7177 OutputType: proto.String("EmptyMessage"), 7178 } 7179 7180 unsupportedBarMsgDesc := &descriptorpb.DescriptorProto{ 7181 Name: proto.String("UnsupportedBarRequest"), 7182 Field: []*descriptorpb.FieldDescriptorProto{ 7183 field1, 7184 }, 7185 } 7186 unsupportedBarMsg := &descriptor.Message{ 7187 DescriptorProto: unsupportedBarMsgDesc, 7188 } 7189 unsupportedBar := &descriptorpb.MethodDescriptorProto{ 7190 Name: proto.String("UnsupportedBar"), 7191 InputType: proto.String("UnsupportedBarRequest"), 7192 OutputType: proto.String("EmptyMessage"), 7193 } 7194 7195 svc1 := &descriptorpb.ServiceDescriptorProto{ 7196 Name: proto.String("Service1"), 7197 Method: []*descriptorpb.MethodDescriptorProto{unsupportedFoo, unsupportedBar}, 7198 } 7199 7200 emptyMsgDesc := &descriptorpb.DescriptorProto{ 7201 Name: proto.String("EmptyMessage"), 7202 } 7203 emptyMsg := &descriptor.Message{ 7204 DescriptorProto: emptyMsgDesc, 7205 } 7206 7207 file := descriptor.File{ 7208 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 7209 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 7210 Name: proto.String("service1.proto"), 7211 Package: proto.String("example"), 7212 MessageType: []*descriptorpb.DescriptorProto{unsupportedBarMsgDesc, unsupportedFooMsgDesc, emptyMsgDesc}, 7213 Service: []*descriptorpb.ServiceDescriptorProto{svc1}, 7214 Options: &descriptorpb.FileOptions{ 7215 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 7216 }, 7217 }, 7218 GoPkg: descriptor.GoPackage{ 7219 Path: "example.com/path/to/example/example.pb", 7220 Name: "example_pb", 7221 }, 7222 Messages: []*descriptor.Message{unsupportedFooMsg, unsupportedBarMsg, emptyMsg}, 7223 Services: []*descriptor.Service{ 7224 { 7225 ServiceDescriptorProto: svc1, 7226 Methods: []*descriptor.Method{ 7227 { 7228 MethodDescriptorProto: unsupportedFoo, 7229 RequestType: unsupportedFooMsg, 7230 ResponseType: unsupportedFooMsg, 7231 Bindings: []*descriptor.Binding{ 7232 { 7233 HTTPMethod: "UNSUPPORTED", 7234 PathTmpl: httprule.Template{ 7235 Version: 1, 7236 OpCodes: []int{0, 0}, 7237 Template: "/v1/{name=foos/*}", 7238 }, 7239 PathParams: []descriptor.Parameter{ 7240 { 7241 Target: &descriptor.Field{ 7242 FieldDescriptorProto: field1, 7243 Message: unsupportedFooMsg, 7244 }, 7245 FieldPath: descriptor.FieldPath{ 7246 { 7247 Name: "name", 7248 }, 7249 }, 7250 }, 7251 }, 7252 Body: &descriptor.Body{ 7253 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7254 }, 7255 }, 7256 }, 7257 }, 7258 { 7259 MethodDescriptorProto: unsupportedBar, 7260 RequestType: unsupportedBarMsg, 7261 ResponseType: unsupportedBarMsg, 7262 Bindings: []*descriptor.Binding{ 7263 { 7264 HTTPMethod: "UNSUPPORTED", 7265 PathTmpl: httprule.Template{ 7266 Version: 1, 7267 OpCodes: []int{0, 0}, 7268 Template: "/v1/{name=bars/*}", 7269 }, 7270 PathParams: []descriptor.Parameter{ 7271 { 7272 Target: &descriptor.Field{ 7273 FieldDescriptorProto: field1, 7274 Message: unsupportedBarMsg, 7275 }, 7276 FieldPath: descriptor.FieldPath{ 7277 { 7278 Name: "name", 7279 }, 7280 }, 7281 }, 7282 }, 7283 Body: &descriptor.Body{ 7284 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7285 }, 7286 }, 7287 }, 7288 }, 7289 }, 7290 }, 7291 }, 7292 } 7293 reg := descriptor.NewRegistry() 7294 err := reg.Load(reqFromFile(&file)) 7295 if err != nil { 7296 t.Fatalf("failed to reg.Load(): %v", err) 7297 } 7298 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 7299 if err != nil { 7300 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 7301 } 7302 7303 // Just should not crash, no special handling of unsupported HTTP methods 7304 if got, want := len(result.Paths), 1; got != want { 7305 t.Fatalf("Results path length differed, got %d want %d", got, want) 7306 } 7307 } 7308 7309 func TestTemplateWithDuplicateHttp1Operations(t *testing.T) { 7310 fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING 7311 field1 := &descriptorpb.FieldDescriptorProto{ 7312 Name: proto.String("name"), 7313 Number: proto.Int32(1), 7314 Type: &fieldType, 7315 } 7316 field2 := &descriptorpb.FieldDescriptorProto{ 7317 Name: proto.String("role"), 7318 Number: proto.Int32(2), 7319 Type: &fieldType, 7320 } 7321 msgdesc := &descriptorpb.DescriptorProto{ 7322 Name: proto.String("ExampleMessage"), 7323 Field: []*descriptorpb.FieldDescriptorProto{ 7324 field1, 7325 field2, 7326 }, 7327 } 7328 meth1 := &descriptorpb.MethodDescriptorProto{ 7329 Name: proto.String("Method1"), 7330 InputType: proto.String("ExampleMessage"), 7331 OutputType: proto.String("ExampleMessage"), 7332 } 7333 meth2 := &descriptorpb.MethodDescriptorProto{ 7334 Name: proto.String("Method2"), 7335 InputType: proto.String("ExampleMessage"), 7336 OutputType: proto.String("ExampleMessage"), 7337 } 7338 svc1 := &descriptorpb.ServiceDescriptorProto{ 7339 Name: proto.String("Service1"), 7340 Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2}, 7341 } 7342 meth3 := &descriptorpb.MethodDescriptorProto{ 7343 Name: proto.String("Method3"), 7344 InputType: proto.String("ExampleMessage"), 7345 OutputType: proto.String("ExampleMessage"), 7346 } 7347 meth4 := &descriptorpb.MethodDescriptorProto{ 7348 Name: proto.String("Method4"), 7349 InputType: proto.String("ExampleMessage"), 7350 OutputType: proto.String("ExampleMessage"), 7351 } 7352 svc2 := &descriptorpb.ServiceDescriptorProto{ 7353 Name: proto.String("Service2"), 7354 Method: []*descriptorpb.MethodDescriptorProto{meth3, meth4}, 7355 } 7356 msg := &descriptor.Message{ 7357 DescriptorProto: msgdesc, 7358 } 7359 7360 file := descriptor.File{ 7361 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 7362 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 7363 Name: proto.String("service1.proto"), 7364 Package: proto.String("example"), 7365 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 7366 Service: []*descriptorpb.ServiceDescriptorProto{svc1, svc2}, 7367 Options: &descriptorpb.FileOptions{ 7368 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 7369 }, 7370 }, 7371 GoPkg: descriptor.GoPackage{ 7372 Path: "example.com/path/to/example/example.pb", 7373 Name: "example_pb", 7374 }, 7375 Messages: []*descriptor.Message{msg}, 7376 Services: []*descriptor.Service{ 7377 { 7378 ServiceDescriptorProto: svc1, 7379 Methods: []*descriptor.Method{ 7380 { 7381 MethodDescriptorProto: meth1, 7382 RequestType: msg, 7383 ResponseType: msg, 7384 Bindings: []*descriptor.Binding{ 7385 { 7386 HTTPMethod: "GET", 7387 PathTmpl: httprule.Template{ 7388 Version: 1, 7389 OpCodes: []int{0, 0}, 7390 Template: "/v1/{name=organizations/*}/{role=roles/*}", 7391 }, 7392 PathParams: []descriptor.Parameter{ 7393 { 7394 Target: &descriptor.Field{ 7395 FieldDescriptorProto: field1, 7396 Message: msg, 7397 }, 7398 FieldPath: descriptor.FieldPath{ 7399 { 7400 Name: "name", 7401 }, 7402 }, 7403 }, 7404 { 7405 Target: &descriptor.Field{ 7406 FieldDescriptorProto: field2, 7407 Message: msg, 7408 }, 7409 FieldPath: descriptor.FieldPath{ 7410 { 7411 Name: "role", 7412 }, 7413 }, 7414 }, 7415 }, 7416 Body: &descriptor.Body{ 7417 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7418 }, 7419 }, 7420 }, 7421 }, 7422 { 7423 MethodDescriptorProto: meth2, 7424 RequestType: msg, 7425 ResponseType: msg, 7426 Bindings: []*descriptor.Binding{ 7427 { 7428 HTTPMethod: "GET", 7429 PathTmpl: httprule.Template{ 7430 Version: 1, 7431 OpCodes: []int{0, 0}, 7432 Template: "/v1/{name=users/*}/{role=roles/*}", 7433 }, 7434 PathParams: []descriptor.Parameter{ 7435 { 7436 Target: &descriptor.Field{ 7437 FieldDescriptorProto: field1, 7438 Message: msg, 7439 }, 7440 FieldPath: descriptor.FieldPath{ 7441 { 7442 Name: "name", 7443 }, 7444 }, 7445 }, 7446 { 7447 Target: &descriptor.Field{ 7448 FieldDescriptorProto: field2, 7449 Message: msg, 7450 }, 7451 FieldPath: descriptor.FieldPath{ 7452 { 7453 Name: "role", 7454 }, 7455 }, 7456 }, 7457 }, 7458 Body: &descriptor.Body{ 7459 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7460 }, 7461 }, 7462 }, 7463 }, 7464 }, 7465 }, 7466 { 7467 ServiceDescriptorProto: svc2, 7468 Methods: []*descriptor.Method{ 7469 { 7470 MethodDescriptorProto: meth3, 7471 RequestType: msg, 7472 ResponseType: msg, 7473 Bindings: []*descriptor.Binding{ 7474 { 7475 HTTPMethod: "GET", 7476 PathTmpl: httprule.Template{ 7477 Version: 1, 7478 OpCodes: []int{0, 0}, 7479 Template: "/v1/{name=users/*}/roles", 7480 }, 7481 PathParams: []descriptor.Parameter{ 7482 { 7483 Target: &descriptor.Field{ 7484 FieldDescriptorProto: field1, 7485 Message: msg, 7486 }, 7487 FieldPath: descriptor.FieldPath{ 7488 { 7489 Name: "name", 7490 }, 7491 }, 7492 }, 7493 }, 7494 Body: &descriptor.Body{ 7495 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7496 }, 7497 }, 7498 }, 7499 }, 7500 { 7501 MethodDescriptorProto: meth4, 7502 RequestType: msg, 7503 ResponseType: msg, 7504 Bindings: []*descriptor.Binding{ 7505 { 7506 HTTPMethod: "GET", 7507 PathTmpl: httprule.Template{ 7508 Version: 1, 7509 OpCodes: []int{0, 0}, 7510 Template: "/v1/{name=groups/*}/{role=roles/*}", 7511 }, 7512 PathParams: []descriptor.Parameter{ 7513 { 7514 Target: &descriptor.Field{ 7515 FieldDescriptorProto: field1, 7516 Message: msg, 7517 }, 7518 FieldPath: descriptor.FieldPath{ 7519 { 7520 Name: "name", 7521 }, 7522 }, 7523 }, 7524 { 7525 Target: &descriptor.Field{ 7526 FieldDescriptorProto: field2, 7527 Message: msg, 7528 }, 7529 FieldPath: descriptor.FieldPath{ 7530 { 7531 Name: "role", 7532 }, 7533 }, 7534 }, 7535 }, 7536 Body: &descriptor.Body{ 7537 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 7538 }, 7539 }, 7540 }, 7541 }, 7542 }, 7543 }, 7544 }, 7545 } 7546 reg := descriptor.NewRegistry() 7547 err := reg.Load(reqFromFile(&file)) 7548 if err != nil { 7549 t.Fatalf("failed to reg.Load(): %v", err) 7550 } 7551 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 7552 if err != nil { 7553 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 7554 } 7555 7556 if got, want := len(result.Paths), 4; got != want { 7557 t.Fatalf("Results path length differed, got %d want %d", got, want) 7558 } 7559 7560 firstOp := result.getPathItemObject("/v1/{name}/{role}").Get 7561 if got, want := firstOp.OperationID, "Service1_Method1"; got != want { 7562 t.Fatalf("First operation id differed, got %s want %s", got, want) 7563 } 7564 if got, want := len(firstOp.Parameters), 3; got != want { 7565 t.Fatalf("First operation params length differed, got %d want %d", got, want) 7566 } 7567 if got, want := firstOp.Parameters[0].Name, "name"; got != want { 7568 t.Fatalf("First operation first param name differed, got %s want %s", got, want) 7569 } 7570 if got, want := firstOp.Parameters[0].Pattern, "organizations/[^/]+"; got != want { 7571 t.Fatalf("First operation first param pattern differed, got %s want %s", got, want) 7572 } 7573 if got, want := firstOp.Parameters[1].Name, "role"; got != want { 7574 t.Fatalf("First operation second param name differed, got %s want %s", got, want) 7575 } 7576 if got, want := firstOp.Parameters[1].Pattern, "roles/[^/]+"; got != want { 7577 t.Fatalf("First operation second param pattern differed, got %s want %s", got, want) 7578 } 7579 if got, want := firstOp.Parameters[2].In, "body"; got != want { 7580 t.Fatalf("First operation third param 'in' differed, got %s want %s", got, want) 7581 } 7582 7583 secondOp := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}/{role}").Get 7584 if got, want := secondOp.OperationID, "Service1_Method2"; got != want { 7585 t.Fatalf("Second operation id differed, got %s want %s", got, want) 7586 } 7587 if got, want := len(secondOp.Parameters), 3; got != want { 7588 t.Fatalf("Second operation params length differed, got %d want %d", got, want) 7589 } 7590 if got, want := secondOp.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want { 7591 t.Fatalf("Second operation first param name differed, got %s want %s", got, want) 7592 } 7593 if got, want := secondOp.Parameters[0].Pattern, "users/[^/]+"; got != want { 7594 t.Fatalf("Second operation first param pattern differed, got %s want %s", got, want) 7595 } 7596 if got, want := secondOp.Parameters[1].Name, "role"; got != want { 7597 t.Fatalf("Second operation second param name differed, got %s want %s", got, want) 7598 } 7599 if got, want := secondOp.Parameters[1].Pattern, "roles/[^/]+"; got != want { 7600 t.Fatalf("Second operation second param pattern differed, got %s want %s", got, want) 7601 } 7602 if got, want := secondOp.Parameters[2].In, "body"; got != want { 7603 t.Fatalf("Second operation third param 'in' differed, got %s want %s", got, want) 7604 } 7605 7606 thirdOp := result.getPathItemObject("/v1/{name}/roles").Get 7607 if got, want := thirdOp.OperationID, "Service2_Method3"; got != want { 7608 t.Fatalf("Third operation id differed, got %s want %s", got, want) 7609 } 7610 if got, want := len(thirdOp.Parameters), 2; got != want { 7611 t.Fatalf("Third operation params length differed, got %d want %d", got, want) 7612 } 7613 if got, want := thirdOp.Parameters[0].Name, "name"; got != want { 7614 t.Fatalf("Third operation first param name differed, got %s want %s", got, want) 7615 } 7616 if got, want := thirdOp.Parameters[0].Pattern, "users/[^/]+"; got != want { 7617 t.Fatalf("Third operation first param pattern differed, got %s want %s", got, want) 7618 } 7619 if got, want := thirdOp.Parameters[1].In, "body"; got != want { 7620 t.Fatalf("Third operation second param 'in' differed, got %s want %s", got, want) 7621 } 7622 7623 forthOp := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "2}/{role}").Get 7624 if got, want := forthOp.OperationID, "Service2_Method4"; got != want { 7625 t.Fatalf("Fourth operation id differed, got %s want %s", got, want) 7626 } 7627 if got, want := len(forthOp.Parameters), 3; got != want { 7628 t.Fatalf("Fourth operation params length differed, got %d want %d", got, want) 7629 } 7630 if got, want := forthOp.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"2"; got != want { 7631 t.Fatalf("Fourth operation first param name differed, got %s want %s", got, want) 7632 } 7633 if got, want := forthOp.Parameters[0].Pattern, "groups/[^/]+"; got != want { 7634 t.Fatalf("Fourth operation first param pattern differed, got %s want %s", got, want) 7635 } 7636 if got, want := forthOp.Parameters[1].Name, "role"; got != want { 7637 t.Fatalf("Fourth operation second param name differed, got %s want %s", got, want) 7638 } 7639 if got, want := forthOp.Parameters[1].Pattern, "roles/[^/]+"; got != want { 7640 t.Fatalf("Fourth operation second param pattern differed, got %s want %s", got, want) 7641 } 7642 if got, want := forthOp.Parameters[2].In, "body"; got != want { 7643 t.Fatalf("Fourth operation second param 'in' differed, got %s want %s", got, want) 7644 } 7645 } 7646 7647 func Test_getReservedJsonName(t *testing.T) { 7648 type args struct { 7649 fieldName string 7650 messageNameToFieldsToJSONName map[string]map[string]string 7651 fieldNameToType map[string]string 7652 } 7653 tests := []struct { 7654 name string 7655 args args 7656 want string 7657 }{ 7658 { 7659 "test case 1: single dot use case", 7660 args{ 7661 fieldName: "abc.a_1", 7662 messageNameToFieldsToJSONName: map[string]map[string]string{ 7663 "Msg": { 7664 "a_1": "a1JSONNAME", 7665 "b_1": "b1JSONNAME", 7666 }, 7667 }, 7668 fieldNameToType: map[string]string{ 7669 "abc": "pkg1.test.Msg", 7670 "bcd": "pkg1.test.Msg", 7671 }, 7672 }, 7673 "a1JSONNAME", 7674 }, 7675 { 7676 "test case 2: single dot use case with no existing field", 7677 args{ 7678 fieldName: "abc.d_1", 7679 messageNameToFieldsToJSONName: map[string]map[string]string{ 7680 "Msg": { 7681 "a_1": "a1JSONNAME", 7682 "b_1": "b1JSONNAME", 7683 }, 7684 }, 7685 fieldNameToType: map[string]string{ 7686 "abc": "pkg1.test.Msg", 7687 "bcd": "pkg1.test.Msg", 7688 }, 7689 }, 7690 "", 7691 }, 7692 { 7693 "test case 3: double dot use case", 7694 args{ 7695 fieldName: "pkg.abc.a_1", 7696 messageNameToFieldsToJSONName: map[string]map[string]string{ 7697 "Msg": { 7698 "a_1": "a1JSONNAME", 7699 "b_1": "b1JSONNAME", 7700 }, 7701 }, 7702 fieldNameToType: map[string]string{ 7703 "abc": "pkg1.test.Msg", 7704 "bcd": "pkg1.test.Msg", 7705 }, 7706 }, 7707 "a1JSONNAME", 7708 }, 7709 { 7710 "test case 4: double dot use case with a not existed field", 7711 args{ 7712 fieldName: "pkg.abc.c_1", 7713 messageNameToFieldsToJSONName: map[string]map[string]string{ 7714 "Msg": { 7715 "a_1": "a1JSONNAME", 7716 "b_1": "b1JSONNAME", 7717 }, 7718 }, 7719 fieldNameToType: map[string]string{ 7720 "abc": "pkg1.test.Msg", 7721 "bcd": "pkg1.test.Msg", 7722 }, 7723 }, 7724 "", 7725 }, 7726 } 7727 for _, tt := range tests { 7728 t.Run(tt.name, func(t *testing.T) { 7729 if got := getReservedJSONName(tt.args.fieldName, tt.args.messageNameToFieldsToJSONName, tt.args.fieldNameToType); got != tt.want { 7730 t.Errorf("getReservedJSONName() = %v, want %v", got, tt.want) 7731 } 7732 }) 7733 } 7734 } 7735 7736 func TestParseIncompleteSecurityRequirement(t *testing.T) { 7737 swagger := openapi_options.Swagger{ 7738 Security: []*openapi_options.SecurityRequirement{ 7739 { 7740 SecurityRequirement: map[string]*openapi_options.SecurityRequirement_SecurityRequirementValue{ 7741 "key": nil, 7742 }, 7743 }, 7744 }, 7745 } 7746 file := descriptor.File{ 7747 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 7748 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 7749 Name: proto.String("example.proto"), 7750 Package: proto.String("example"), 7751 Options: &descriptorpb.FileOptions{ 7752 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 7753 }, 7754 }, 7755 } 7756 proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger) 7757 reg := descriptor.NewRegistry() 7758 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 7759 if err != nil { 7760 t.Errorf("failed to reg.Load(): %v", err) 7761 return 7762 } 7763 _, err = applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 7764 if err == nil { 7765 t.Errorf("applyTemplate(%#v) did not error as expected", file) 7766 return 7767 } 7768 } 7769 7770 func TestSubPathParams(t *testing.T) { 7771 outerParams := []descriptor.Parameter{ 7772 { 7773 FieldPath: []descriptor.FieldPathComponent{ 7774 { 7775 Name: "prefix", 7776 }, 7777 { 7778 Name: "first", 7779 }, 7780 }, 7781 }, 7782 { 7783 FieldPath: []descriptor.FieldPathComponent{ 7784 { 7785 Name: "prefix", 7786 }, 7787 { 7788 Name: "second", 7789 }, 7790 { 7791 Name: "deeper", 7792 }, 7793 }, 7794 }, 7795 { 7796 FieldPath: []descriptor.FieldPathComponent{ 7797 { 7798 Name: "otherprefix", 7799 }, 7800 { 7801 Name: "third", 7802 }, 7803 }, 7804 }, 7805 } 7806 subParams := subPathParams("prefix", outerParams) 7807 7808 if got, want := len(subParams), 2; got != want { 7809 t.Fatalf("Wrong number of path params, got %d want %d", got, want) 7810 } 7811 if got, want := len(subParams[0].FieldPath), 1; got != want { 7812 t.Fatalf("Wrong length of path param 0, got %d want %d", got, want) 7813 } 7814 if got, want := subParams[0].FieldPath[0].Name, "first"; got != want { 7815 t.Fatalf("Wrong path param 0, element 0, got %s want %s", got, want) 7816 } 7817 if got, want := len(subParams[1].FieldPath), 2; got != want { 7818 t.Fatalf("Wrong length of path param 1 got %d want %d", got, want) 7819 } 7820 if got, want := subParams[1].FieldPath[0].Name, "second"; got != want { 7821 t.Fatalf("Wrong path param 1, element 0, got %s want %s", got, want) 7822 } 7823 if got, want := subParams[1].FieldPath[1].Name, "deeper"; got != want { 7824 t.Fatalf("Wrong path param 1, element 1, got %s want %s", got, want) 7825 } 7826 } 7827 7828 func TestRenderServicesParameterDescriptionNoFieldBody(t *testing.T) { 7829 7830 optionsRaw := 7831 `{ 7832 "[grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema]": { 7833 "jsonSchema": { 7834 "title": "aMessage title", 7835 "description": "aMessage description" 7836 } 7837 } 7838 }` 7839 7840 options := &descriptorpb.MessageOptions{} 7841 err := protojson.Unmarshal([]byte(optionsRaw), options) 7842 if err != nil { 7843 t.Fatalf("Error while unmarshalling options: %s", err.Error()) 7844 } 7845 7846 aMessageDesc := &descriptorpb.DescriptorProto{ 7847 Name: proto.String("AMessage"), 7848 Field: []*descriptorpb.FieldDescriptorProto{ 7849 { 7850 Name: proto.String("project_id"), 7851 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 7852 Number: proto.Int32(1), 7853 }, 7854 { 7855 Name: proto.String("other_field"), 7856 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 7857 Number: proto.Int32(2), 7858 }, 7859 }, 7860 Options: options, 7861 } 7862 someResponseDesc := &descriptorpb.DescriptorProto{ 7863 Name: proto.String("SomeResponse"), 7864 } 7865 aMeth := &descriptorpb.MethodDescriptorProto{ 7866 Name: proto.String("AMethod"), 7867 InputType: proto.String("AMessage"), 7868 OutputType: proto.String("SomeResponse"), 7869 } 7870 svc := &descriptorpb.ServiceDescriptorProto{ 7871 Name: proto.String("Test"), 7872 Method: []*descriptorpb.MethodDescriptorProto{aMeth}, 7873 } 7874 aMessage := &descriptor.Message{ 7875 DescriptorProto: aMessageDesc, 7876 } 7877 someResponseMessage := &descriptor.Message{ 7878 DescriptorProto: someResponseDesc, 7879 } 7880 7881 file := descriptor.File{ 7882 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 7883 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 7884 Package: proto.String("api"), 7885 Name: proto.String("test.proto"), 7886 MessageType: []*descriptorpb.DescriptorProto{aMessageDesc, someResponseDesc}, 7887 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 7888 Options: &descriptorpb.FileOptions{ 7889 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 7890 }, 7891 }, 7892 GoPkg: descriptor.GoPackage{ 7893 Path: "example.com/path/to/example/example.pb", 7894 Name: "example_pb", 7895 }, 7896 Messages: []*descriptor.Message{aMessage, someResponseMessage}, 7897 Services: []*descriptor.Service{ 7898 { 7899 ServiceDescriptorProto: svc, 7900 Methods: []*descriptor.Method{ 7901 { 7902 MethodDescriptorProto: aMeth, 7903 RequestType: aMessage, 7904 ResponseType: someResponseMessage, 7905 Bindings: []*descriptor.Binding{ 7906 { 7907 HTTPMethod: "POST", 7908 PathTmpl: httprule.Template{ 7909 Version: 1, 7910 OpCodes: []int{0, 0}, 7911 Template: "/v1/projects/someotherpath", 7912 }, 7913 Body: &descriptor.Body{}, 7914 }, 7915 }, 7916 }, 7917 }, 7918 }, 7919 }, 7920 } 7921 reg := descriptor.NewRegistry() 7922 reg.SetUseJSONNamesForFields(true) 7923 err = reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 7924 if err != nil { 7925 t.Fatalf("failed to reg.Load(): %v", err) 7926 } 7927 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 7928 if err != nil { 7929 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 7930 } 7931 7932 got := result.getPathItemObject("/v1/projects/someotherpath").Post.Parameters[0].Description 7933 want := "aMessage description" 7934 7935 if got != want { 7936 t.Fatalf("Wrong description for body parameter, got %s want %s", got, want) 7937 } 7938 7939 } 7940 7941 func TestRenderServicesWithBodyFieldNameInCamelCase(t *testing.T) { 7942 userDesc := &descriptorpb.DescriptorProto{ 7943 Name: proto.String("User"), 7944 Field: []*descriptorpb.FieldDescriptorProto{ 7945 { 7946 Name: proto.String("name"), 7947 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 7948 Number: proto.Int32(1), 7949 }, 7950 { 7951 Name: proto.String("role"), 7952 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 7953 Number: proto.Int32(2), 7954 }, 7955 }, 7956 } 7957 updateDesc := &descriptorpb.DescriptorProto{ 7958 Name: proto.String("UpdateUserRequest"), 7959 Field: []*descriptorpb.FieldDescriptorProto{ 7960 { 7961 Name: proto.String("user_object"), 7962 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 7963 TypeName: proto.String(".example.User"), 7964 Number: proto.Int32(1), 7965 }, 7966 }, 7967 } 7968 meth := &descriptorpb.MethodDescriptorProto{ 7969 Name: proto.String("UpdateUser"), 7970 InputType: proto.String("UpdateUserRequest"), 7971 OutputType: proto.String("User"), 7972 } 7973 svc := &descriptorpb.ServiceDescriptorProto{ 7974 Name: proto.String("UserService"), 7975 Method: []*descriptorpb.MethodDescriptorProto{meth}, 7976 } 7977 userMsg := &descriptor.Message{ 7978 DescriptorProto: userDesc, 7979 } 7980 updateMsg := &descriptor.Message{ 7981 DescriptorProto: updateDesc, 7982 } 7983 nameField := &descriptor.Field{ 7984 Message: userMsg, 7985 FieldDescriptorProto: userMsg.GetField()[0], 7986 } 7987 nameField.JsonName = proto.String("name") 7988 roleField := &descriptor.Field{ 7989 Message: userMsg, 7990 FieldDescriptorProto: userMsg.GetField()[1], 7991 } 7992 roleField.JsonName = proto.String("role") 7993 userMsg.Fields = []*descriptor.Field{nameField, roleField} 7994 userField := &descriptor.Field{ 7995 Message: updateMsg, 7996 FieldMessage: userMsg, 7997 FieldDescriptorProto: updateMsg.GetField()[0], 7998 } 7999 userField.JsonName = proto.String("userObject") 8000 updateMsg.Fields = []*descriptor.Field{userField} 8001 8002 file := descriptor.File{ 8003 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8004 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8005 Package: proto.String("example"), 8006 Name: proto.String("user_service.proto"), 8007 MessageType: []*descriptorpb.DescriptorProto{userDesc, updateDesc}, 8008 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8009 Options: &descriptorpb.FileOptions{ 8010 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8011 }, 8012 }, 8013 GoPkg: descriptor.GoPackage{ 8014 Path: "example.com/path/to/example/example.pb", 8015 Name: "example_pb", 8016 }, 8017 Messages: []*descriptor.Message{userMsg, updateMsg}, 8018 Services: []*descriptor.Service{ 8019 { 8020 ServiceDescriptorProto: svc, 8021 Methods: []*descriptor.Method{ 8022 { 8023 MethodDescriptorProto: meth, 8024 RequestType: updateMsg, 8025 ResponseType: userMsg, 8026 Bindings: []*descriptor.Binding{ 8027 { 8028 HTTPMethod: "POST", 8029 PathTmpl: httprule.Template{ 8030 Version: 1, 8031 OpCodes: []int{0, 0}, 8032 Template: "/v1/users/{user_object.name}", 8033 }, 8034 PathParams: []descriptor.Parameter{ 8035 { 8036 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8037 { 8038 Name: "user_object", 8039 }, 8040 { 8041 Name: "name", 8042 }, 8043 }), 8044 Target: nameField, 8045 }, 8046 }, 8047 Body: &descriptor.Body{ 8048 FieldPath: []descriptor.FieldPathComponent{ 8049 { 8050 Name: "user_object", 8051 Target: userField, 8052 }, 8053 }, 8054 }, 8055 }, 8056 }, 8057 }, 8058 }, 8059 }, 8060 }, 8061 } 8062 reg := descriptor.NewRegistry() 8063 reg.SetUseJSONNamesForFields(true) 8064 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 8065 if err != nil { 8066 t.Fatalf("failed to reg.Load(): %v", err) 8067 } 8068 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8069 if err != nil { 8070 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8071 } 8072 8073 paths := GetPaths(result) 8074 if got, want := len(paths), 1; got != want { 8075 t.Fatalf("Results path length differed, got %d want %d", got, want) 8076 } 8077 8078 if got, want := paths[0], "/v1/users/{userObject.name}"; got != want { 8079 t.Fatalf("Wrong results path, got %s want %s", got, want) 8080 } 8081 8082 var operation = *result.getPathItemObject("/v1/users/{userObject.name}").Post 8083 if got, want := len(operation.Parameters), 2; got != want { 8084 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8085 } 8086 8087 if got, want := operation.Parameters[0].Name, "userObject.name"; got != want { 8088 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8089 } 8090 8091 if got, want := operation.Parameters[0].In, "path"; got != want { 8092 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8093 } 8094 8095 if got, want := operation.Parameters[1].Name, "userObject"; got != want { 8096 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8097 } 8098 8099 if got, want := operation.Parameters[1].In, "body"; got != want { 8100 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8101 } 8102 8103 // The body parameter should be inlined and not contain 'name', as this is a path parameter. 8104 schema := operation.Parameters[1].Schema 8105 if got, want := schema.Ref, ""; got != want { 8106 t.Fatalf("Wrong reference, got %s want %s", got, want) 8107 } 8108 props := schema.Properties 8109 if props == nil { 8110 t.Fatal("No properties on body parameter") 8111 } 8112 if got, want := len(*props), 1; got != want { 8113 t.Fatalf("Properties length differed, got %d want %d", got, want) 8114 } 8115 for _, v := range *props { 8116 if got, want := v.Key, "role"; got != want { 8117 t.Fatalf("Wrong key for property, got %s want %s", got, want) 8118 } 8119 } 8120 } 8121 8122 func TestRenderServicesWithBodyFieldHasFieldMask(t *testing.T) { 8123 userDesc := &descriptorpb.DescriptorProto{ 8124 Name: proto.String("User"), 8125 Field: []*descriptorpb.FieldDescriptorProto{ 8126 { 8127 Name: proto.String("name"), 8128 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8129 Number: proto.Int32(1), 8130 }, 8131 { 8132 Name: proto.String("role"), 8133 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8134 Number: proto.Int32(2), 8135 }, 8136 }, 8137 } 8138 updateDesc := &descriptorpb.DescriptorProto{ 8139 Name: proto.String("UpdateUserRequest"), 8140 Field: []*descriptorpb.FieldDescriptorProto{ 8141 { 8142 Name: proto.String("user_object"), 8143 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 8144 TypeName: proto.String(".example.User"), 8145 Number: proto.Int32(1), 8146 }, 8147 { 8148 Name: proto.String("update_mask"), 8149 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 8150 TypeName: proto.String(".google.protobuf.FieldMask"), 8151 Number: proto.Int32(2), 8152 }, 8153 }, 8154 } 8155 8156 meth := &descriptorpb.MethodDescriptorProto{ 8157 Name: proto.String("UpdateUser"), 8158 InputType: proto.String("UpdateUserRequest"), 8159 OutputType: proto.String("User"), 8160 } 8161 svc := &descriptorpb.ServiceDescriptorProto{ 8162 Name: proto.String("UserService"), 8163 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8164 } 8165 userMsg := &descriptor.Message{ 8166 DescriptorProto: userDesc, 8167 } 8168 updateMsg := &descriptor.Message{ 8169 DescriptorProto: updateDesc, 8170 } 8171 nameField := &descriptor.Field{ 8172 Message: userMsg, 8173 FieldDescriptorProto: userMsg.GetField()[0], 8174 } 8175 nameField.JsonName = proto.String("name") 8176 roleField := &descriptor.Field{ 8177 Message: userMsg, 8178 FieldDescriptorProto: userMsg.GetField()[1], 8179 } 8180 roleField.JsonName = proto.String("role") 8181 userMsg.Fields = []*descriptor.Field{nameField, roleField} 8182 userField := &descriptor.Field{ 8183 Message: updateMsg, 8184 FieldMessage: userMsg, 8185 FieldDescriptorProto: updateMsg.GetField()[0], 8186 } 8187 userField.JsonName = proto.String("userObject") 8188 updateMaskField := &descriptor.Field{ 8189 Message: updateMsg, 8190 FieldDescriptorProto: updateMsg.GetField()[1], 8191 } 8192 updateMaskField.JsonName = proto.String("updateMask") 8193 updateMsg.Fields = []*descriptor.Field{userField, updateMaskField} 8194 8195 file := descriptor.File{ 8196 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8197 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8198 Package: proto.String("example"), 8199 Name: proto.String("user_service.proto"), 8200 Dependency: []string{"google/well_known.proto"}, 8201 MessageType: []*descriptorpb.DescriptorProto{userDesc, updateDesc}, 8202 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8203 Options: &descriptorpb.FileOptions{ 8204 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8205 }, 8206 }, 8207 GoPkg: descriptor.GoPackage{ 8208 Path: "example.com/path/to/example/example.pb", 8209 Name: "example_pb", 8210 }, 8211 Messages: []*descriptor.Message{userMsg, updateMsg}, 8212 Services: []*descriptor.Service{ 8213 { 8214 ServiceDescriptorProto: svc, 8215 Methods: []*descriptor.Method{ 8216 { 8217 MethodDescriptorProto: meth, 8218 RequestType: updateMsg, 8219 ResponseType: userMsg, 8220 Bindings: []*descriptor.Binding{ 8221 { 8222 HTTPMethod: "PATCH", 8223 PathTmpl: httprule.Template{ 8224 Version: 1, 8225 OpCodes: []int{0, 0}, 8226 Template: "/v1/users/{user_object.name}", 8227 }, 8228 PathParams: []descriptor.Parameter{ 8229 { 8230 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8231 { 8232 Name: "user_object", 8233 }, 8234 { 8235 Name: "name", 8236 }, 8237 }), 8238 Target: nameField, 8239 }, 8240 }, 8241 Body: &descriptor.Body{ 8242 FieldPath: []descriptor.FieldPathComponent{ 8243 { 8244 Name: "user_object", 8245 Target: userField, 8246 }, 8247 }, 8248 }, 8249 }, 8250 }, 8251 }, 8252 }, 8253 }, 8254 }, 8255 } 8256 reg := descriptor.NewRegistry() 8257 reg.SetUseJSONNamesForFields(true) 8258 reg.SetAllowPatchFeature(true) 8259 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{ 8260 { 8261 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8262 Name: proto.String("google/well_known.proto"), 8263 Package: proto.String("google.protobuf"), 8264 Dependency: []string{}, 8265 MessageType: []*descriptorpb.DescriptorProto{ 8266 protodesc.ToDescriptorProto((&field_mask.FieldMask{}).ProtoReflect().Descriptor()), 8267 }, 8268 Service: []*descriptorpb.ServiceDescriptorProto{}, 8269 Options: &descriptorpb.FileOptions{ 8270 GoPackage: proto.String("google/well_known"), 8271 }, 8272 }, 8273 file.FileDescriptorProto, 8274 }}) 8275 if err != nil { 8276 t.Fatalf("failed to reg.Load(): %v", err) 8277 } 8278 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8279 if err != nil { 8280 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8281 } 8282 8283 paths := GetPaths(result) 8284 if got, want := len(paths), 1; got != want { 8285 t.Fatalf("Results path length differed, got %d want %d", got, want) 8286 } 8287 8288 if got, want := paths[0], "/v1/users/{userObject.name}"; got != want { 8289 t.Fatalf("Wrong results path, got %s want %s", got, want) 8290 } 8291 8292 var operation = *result.getPathItemObject("/v1/users/{userObject.name}").Patch 8293 if got, want := len(operation.Parameters), 2; got != want { 8294 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8295 } 8296 8297 if got, want := operation.Parameters[0].Name, "userObject.name"; got != want { 8298 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8299 } 8300 8301 if got, want := operation.Parameters[0].In, "path"; got != want { 8302 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8303 } 8304 8305 if got, want := operation.Parameters[1].Name, "userObject"; got != want { 8306 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8307 } 8308 8309 if got, want := operation.Parameters[1].In, "body"; got != want { 8310 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8311 } 8312 } 8313 8314 func TestRenderServicesWithColonInPath(t *testing.T) { 8315 jsonSchema := &openapi_options.JSONSchema{ 8316 FieldConfiguration: &openapi_options.JSONSchema_FieldConfiguration{ 8317 PathParamName: "overrideField", 8318 }, 8319 } 8320 var fieldOptions = new(descriptorpb.FieldOptions) 8321 proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema) 8322 8323 reqDesc := &descriptorpb.DescriptorProto{ 8324 Name: proto.String("MyRequest"), 8325 Field: []*descriptorpb.FieldDescriptorProto{ 8326 { 8327 Name: proto.String("field"), 8328 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8329 Number: proto.Int32(1), 8330 Options: fieldOptions, 8331 }, 8332 }, 8333 } 8334 resDesc := &descriptorpb.DescriptorProto{ 8335 Name: proto.String("MyResponse"), 8336 Field: []*descriptorpb.FieldDescriptorProto{ 8337 { 8338 Name: proto.String("field"), 8339 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8340 Number: proto.Int32(1), 8341 }, 8342 }, 8343 } 8344 meth := &descriptorpb.MethodDescriptorProto{ 8345 Name: proto.String("MyMethod"), 8346 InputType: proto.String("MyRequest"), 8347 OutputType: proto.String("MyResponse"), 8348 } 8349 svc := &descriptorpb.ServiceDescriptorProto{ 8350 Name: proto.String("MyService"), 8351 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8352 } 8353 reqMsg := &descriptor.Message{ 8354 DescriptorProto: reqDesc, 8355 } 8356 resMsg := &descriptor.Message{ 8357 DescriptorProto: resDesc, 8358 } 8359 reqField := &descriptor.Field{ 8360 Message: reqMsg, 8361 FieldDescriptorProto: reqMsg.GetField()[0], 8362 } 8363 resField := &descriptor.Field{ 8364 Message: resMsg, 8365 FieldDescriptorProto: resMsg.GetField()[0], 8366 } 8367 reqField.JsonName = proto.String("field") 8368 resField.JsonName = proto.String("field") 8369 reqMsg.Fields = []*descriptor.Field{reqField} 8370 resMsg.Fields = []*descriptor.Field{resField} 8371 8372 file := descriptor.File{ 8373 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8374 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8375 Package: proto.String("example"), 8376 Name: proto.String(",my_service.proto"), 8377 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 8378 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8379 Options: &descriptorpb.FileOptions{ 8380 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8381 }, 8382 }, 8383 GoPkg: descriptor.GoPackage{ 8384 Path: "example.com/path/to/example/example.pb", 8385 Name: "example_pb", 8386 }, 8387 Messages: []*descriptor.Message{reqMsg, resMsg}, 8388 Services: []*descriptor.Service{ 8389 { 8390 ServiceDescriptorProto: svc, 8391 Methods: []*descriptor.Method{ 8392 { 8393 MethodDescriptorProto: meth, 8394 RequestType: reqMsg, 8395 ResponseType: resMsg, 8396 Bindings: []*descriptor.Binding{ 8397 { 8398 HTTPMethod: "POST", 8399 PathTmpl: httprule.Template{ 8400 Version: 1, 8401 OpCodes: []int{0, 0}, 8402 Template: "/my/{field}:foo", 8403 }, 8404 PathParams: []descriptor.Parameter{ 8405 { 8406 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8407 { 8408 Name: "field", 8409 }, 8410 }), 8411 Target: reqField, 8412 }, 8413 }, 8414 Body: &descriptor.Body{ 8415 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 8416 }, 8417 }, 8418 }, 8419 }, 8420 }, 8421 }, 8422 }, 8423 } 8424 reg := descriptor.NewRegistry() 8425 reg.SetUseJSONNamesForFields(true) 8426 err := reg.Load(reqFromFile(&file)) 8427 if err != nil { 8428 t.Fatalf("failed to reg.Load(): %v", err) 8429 } 8430 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8431 if err != nil { 8432 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8433 } 8434 8435 paths := GetPaths(result) 8436 if got, want := len(paths), 1; got != want { 8437 t.Fatalf("Results path length differed, got %d want %d", got, want) 8438 } 8439 8440 if got, want := paths[0], "/my/{overrideField}:foo"; got != want { 8441 t.Fatalf("Wrong results path, got %s want %s", got, want) 8442 } 8443 8444 var operation = *result.getPathItemObject("/my/{overrideField}:foo").Post 8445 if got, want := len(operation.Parameters), 2; got != want { 8446 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8447 } 8448 8449 if got, want := operation.Parameters[0].Name, "overrideField"; got != want { 8450 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8451 } 8452 8453 if got, want := operation.Parameters[0].In, "path"; got != want { 8454 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8455 } 8456 8457 if got, want := operation.Parameters[0].Type, "string"; got != want { 8458 t.Fatalf("Wrong parameter type, got %s want %s", got, want) 8459 } 8460 8461 if got, want := operation.Parameters[1].Name, "body"; got != want { 8462 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8463 } 8464 8465 if got, want := operation.Parameters[1].In, "body"; got != want { 8466 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8467 } 8468 } 8469 8470 func TestRenderServicesWithDoubleColonInPath(t *testing.T) { 8471 reqDesc := &descriptorpb.DescriptorProto{ 8472 Name: proto.String("MyRequest"), 8473 Field: []*descriptorpb.FieldDescriptorProto{ 8474 { 8475 Name: proto.String("field"), 8476 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8477 Number: proto.Int32(1), 8478 }, 8479 }, 8480 } 8481 resDesc := &descriptorpb.DescriptorProto{ 8482 Name: proto.String("MyResponse"), 8483 Field: []*descriptorpb.FieldDescriptorProto{ 8484 { 8485 Name: proto.String("field"), 8486 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8487 Number: proto.Int32(1), 8488 }, 8489 }, 8490 } 8491 meth := &descriptorpb.MethodDescriptorProto{ 8492 Name: proto.String("MyMethod"), 8493 InputType: proto.String("MyRequest"), 8494 OutputType: proto.String("MyResponse"), 8495 } 8496 svc := &descriptorpb.ServiceDescriptorProto{ 8497 Name: proto.String("MyService"), 8498 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8499 } 8500 reqMsg := &descriptor.Message{ 8501 DescriptorProto: reqDesc, 8502 } 8503 resMsg := &descriptor.Message{ 8504 DescriptorProto: resDesc, 8505 } 8506 reqField := &descriptor.Field{ 8507 Message: reqMsg, 8508 FieldDescriptorProto: reqMsg.GetField()[0], 8509 } 8510 resField := &descriptor.Field{ 8511 Message: resMsg, 8512 FieldDescriptorProto: resMsg.GetField()[0], 8513 } 8514 reqField.JsonName = proto.String("field") 8515 resField.JsonName = proto.String("field") 8516 reqMsg.Fields = []*descriptor.Field{reqField} 8517 resMsg.Fields = []*descriptor.Field{resField} 8518 8519 file := descriptor.File{ 8520 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8521 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8522 Package: proto.String("example"), 8523 Name: proto.String(",my_service.proto"), 8524 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 8525 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8526 Options: &descriptorpb.FileOptions{ 8527 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8528 }, 8529 }, 8530 GoPkg: descriptor.GoPackage{ 8531 Path: "example.com/path/to/example/example.pb", 8532 Name: "example_pb", 8533 }, 8534 Messages: []*descriptor.Message{reqMsg, resMsg}, 8535 Services: []*descriptor.Service{ 8536 { 8537 ServiceDescriptorProto: svc, 8538 Methods: []*descriptor.Method{ 8539 { 8540 MethodDescriptorProto: meth, 8541 RequestType: reqMsg, 8542 ResponseType: resMsg, 8543 Bindings: []*descriptor.Binding{ 8544 { 8545 HTTPMethod: "POST", 8546 PathTmpl: httprule.Template{ 8547 Version: 1, 8548 OpCodes: []int{0, 0}, 8549 Template: "/my/{field}:foo:bar", 8550 }, 8551 PathParams: []descriptor.Parameter{ 8552 { 8553 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8554 { 8555 Name: "field", 8556 }, 8557 }), 8558 Target: reqField, 8559 }, 8560 }, 8561 Body: &descriptor.Body{ 8562 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 8563 }, 8564 }, 8565 }, 8566 }, 8567 }, 8568 }, 8569 }, 8570 } 8571 reg := descriptor.NewRegistry() 8572 reg.SetUseJSONNamesForFields(true) 8573 err := reg.Load(reqFromFile(&file)) 8574 if err != nil { 8575 t.Fatalf("failed to reg.Load(): %v", err) 8576 } 8577 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8578 if err != nil { 8579 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8580 } 8581 8582 paths := GetPaths(result) 8583 if got, want := len(paths), 1; got != want { 8584 t.Fatalf("Results path length differed, got %d want %d", got, want) 8585 } 8586 8587 if got, want := paths[0], "/my/{field}:foo:bar"; got != want { 8588 t.Fatalf("Wrong results path, got %s want %s", got, want) 8589 } 8590 8591 var operation = *result.getPathItemObject("/my/{field}:foo:bar").Post 8592 if got, want := len(operation.Parameters), 2; got != want { 8593 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8594 } 8595 8596 if got, want := operation.Parameters[0].Name, "field"; got != want { 8597 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8598 } 8599 8600 if got, want := operation.Parameters[0].In, "path"; got != want { 8601 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8602 } 8603 8604 if got, want := operation.Parameters[0].Type, "string"; got != want { 8605 t.Fatalf("Wrong parameter type, got %s want %s", got, want) 8606 } 8607 8608 if got, want := operation.Parameters[1].Name, "body"; got != want { 8609 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8610 } 8611 8612 if got, want := operation.Parameters[1].In, "body"; got != want { 8613 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8614 } 8615 } 8616 8617 func TestRenderServicesWithColonLastInPath(t *testing.T) { 8618 reqDesc := &descriptorpb.DescriptorProto{ 8619 Name: proto.String("MyRequest"), 8620 Field: []*descriptorpb.FieldDescriptorProto{ 8621 { 8622 Name: proto.String("field"), 8623 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8624 Number: proto.Int32(1), 8625 }, 8626 }, 8627 } 8628 resDesc := &descriptorpb.DescriptorProto{ 8629 Name: proto.String("MyResponse"), 8630 Field: []*descriptorpb.FieldDescriptorProto{ 8631 { 8632 Name: proto.String("field"), 8633 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8634 Number: proto.Int32(1), 8635 }, 8636 }, 8637 } 8638 meth := &descriptorpb.MethodDescriptorProto{ 8639 Name: proto.String("MyMethod"), 8640 InputType: proto.String("MyRequest"), 8641 OutputType: proto.String("MyResponse"), 8642 } 8643 svc := &descriptorpb.ServiceDescriptorProto{ 8644 Name: proto.String("MyService"), 8645 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8646 } 8647 reqMsg := &descriptor.Message{ 8648 DescriptorProto: reqDesc, 8649 } 8650 resMsg := &descriptor.Message{ 8651 DescriptorProto: resDesc, 8652 } 8653 reqField := &descriptor.Field{ 8654 Message: reqMsg, 8655 FieldDescriptorProto: reqMsg.GetField()[0], 8656 } 8657 resField := &descriptor.Field{ 8658 Message: resMsg, 8659 FieldDescriptorProto: resMsg.GetField()[0], 8660 } 8661 reqField.JsonName = proto.String("field") 8662 resField.JsonName = proto.String("field") 8663 reqMsg.Fields = []*descriptor.Field{reqField} 8664 resMsg.Fields = []*descriptor.Field{resField} 8665 8666 file := descriptor.File{ 8667 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8668 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8669 Package: proto.String("example"), 8670 Name: proto.String(",my_service.proto"), 8671 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 8672 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8673 Options: &descriptorpb.FileOptions{ 8674 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8675 }, 8676 }, 8677 GoPkg: descriptor.GoPackage{ 8678 Path: "example.com/path/to/example/example.pb", 8679 Name: "example_pb", 8680 }, 8681 Messages: []*descriptor.Message{reqMsg, resMsg}, 8682 Services: []*descriptor.Service{ 8683 { 8684 ServiceDescriptorProto: svc, 8685 Methods: []*descriptor.Method{ 8686 { 8687 MethodDescriptorProto: meth, 8688 RequestType: reqMsg, 8689 ResponseType: resMsg, 8690 Bindings: []*descriptor.Binding{ 8691 { 8692 HTTPMethod: "POST", 8693 PathTmpl: httprule.Template{ 8694 Version: 1, 8695 OpCodes: []int{0, 0}, 8696 Template: "/my/{field}:", 8697 }, 8698 PathParams: []descriptor.Parameter{ 8699 { 8700 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8701 { 8702 Name: "field", 8703 }, 8704 }), 8705 Target: reqField, 8706 }, 8707 }, 8708 Body: &descriptor.Body{ 8709 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 8710 }, 8711 }, 8712 }, 8713 }, 8714 }, 8715 }, 8716 }, 8717 } 8718 reg := descriptor.NewRegistry() 8719 reg.SetUseJSONNamesForFields(true) 8720 err := reg.Load(reqFromFile(&file)) 8721 if err != nil { 8722 t.Fatalf("failed to reg.Load(): %v", err) 8723 } 8724 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8725 if err != nil { 8726 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8727 } 8728 8729 paths := GetPaths(result) 8730 if got, want := len(paths), 1; got != want { 8731 t.Fatalf("Results path length differed, got %d want %d", got, want) 8732 } 8733 8734 if got, want := paths[0], "/my/{field}:"; got != want { 8735 t.Fatalf("Wrong results path, got %s want %s", got, want) 8736 } 8737 8738 var operation = *result.getPathItemObject("/my/{field}:").Post 8739 if got, want := len(operation.Parameters), 2; got != want { 8740 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8741 } 8742 8743 if got, want := operation.Parameters[0].Name, "field"; got != want { 8744 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8745 } 8746 8747 if got, want := operation.Parameters[0].In, "path"; got != want { 8748 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8749 } 8750 8751 if got, want := operation.Parameters[0].Type, "string"; got != want { 8752 t.Fatalf("Wrong parameter type, got %s want %s", got, want) 8753 } 8754 8755 if got, want := operation.Parameters[1].Name, "body"; got != want { 8756 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8757 } 8758 8759 if got, want := operation.Parameters[1].In, "body"; got != want { 8760 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8761 } 8762 } 8763 8764 func TestRenderServicesWithColonInSegment(t *testing.T) { 8765 reqDesc := &descriptorpb.DescriptorProto{ 8766 Name: proto.String("MyRequest"), 8767 Field: []*descriptorpb.FieldDescriptorProto{ 8768 { 8769 Name: proto.String("field"), 8770 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8771 Number: proto.Int32(1), 8772 }, 8773 }, 8774 } 8775 resDesc := &descriptorpb.DescriptorProto{ 8776 Name: proto.String("MyResponse"), 8777 Field: []*descriptorpb.FieldDescriptorProto{ 8778 { 8779 Name: proto.String("field"), 8780 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 8781 Number: proto.Int32(1), 8782 }, 8783 }, 8784 } 8785 meth := &descriptorpb.MethodDescriptorProto{ 8786 Name: proto.String("MyMethod"), 8787 InputType: proto.String("MyRequest"), 8788 OutputType: proto.String("MyResponse"), 8789 } 8790 svc := &descriptorpb.ServiceDescriptorProto{ 8791 Name: proto.String("MyService"), 8792 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8793 } 8794 reqMsg := &descriptor.Message{ 8795 DescriptorProto: reqDesc, 8796 } 8797 resMsg := &descriptor.Message{ 8798 DescriptorProto: resDesc, 8799 } 8800 reqField := &descriptor.Field{ 8801 Message: reqMsg, 8802 FieldDescriptorProto: reqMsg.GetField()[0], 8803 } 8804 resField := &descriptor.Field{ 8805 Message: resMsg, 8806 FieldDescriptorProto: resMsg.GetField()[0], 8807 } 8808 reqField.JsonName = proto.String("field") 8809 resField.JsonName = proto.String("field") 8810 reqMsg.Fields = []*descriptor.Field{reqField} 8811 resMsg.Fields = []*descriptor.Field{resField} 8812 8813 file := descriptor.File{ 8814 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8815 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8816 Package: proto.String("example"), 8817 Name: proto.String(",my_service.proto"), 8818 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 8819 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8820 Options: &descriptorpb.FileOptions{ 8821 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8822 }, 8823 }, 8824 GoPkg: descriptor.GoPackage{ 8825 Path: "example.com/path/to/example/example.pb", 8826 Name: "example_pb", 8827 }, 8828 Messages: []*descriptor.Message{reqMsg, resMsg}, 8829 Services: []*descriptor.Service{ 8830 { 8831 ServiceDescriptorProto: svc, 8832 Methods: []*descriptor.Method{ 8833 { 8834 MethodDescriptorProto: meth, 8835 RequestType: reqMsg, 8836 ResponseType: resMsg, 8837 Bindings: []*descriptor.Binding{ 8838 { 8839 HTTPMethod: "POST", 8840 PathTmpl: httprule.Template{ 8841 Version: 1, 8842 OpCodes: []int{0, 0}, 8843 Template: "/my/{field=segment/wi:th}", 8844 }, 8845 PathParams: []descriptor.Parameter{ 8846 { 8847 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{ 8848 { 8849 Name: "field", 8850 }, 8851 }), 8852 Target: reqField, 8853 }, 8854 }, 8855 Body: &descriptor.Body{ 8856 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 8857 }, 8858 }, 8859 }, 8860 }, 8861 }, 8862 }, 8863 }, 8864 } 8865 reg := descriptor.NewRegistry() 8866 reg.SetUseJSONNamesForFields(true) 8867 err := reg.Load(reqFromFile(&file)) 8868 if err != nil { 8869 t.Fatalf("failed to reg.Load(): %v", err) 8870 } 8871 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 8872 if err != nil { 8873 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 8874 } 8875 8876 paths := GetPaths(result) 8877 if got, want := len(paths), 1; got != want { 8878 t.Fatalf("Results path length differed, got %d want %d", got, want) 8879 } 8880 8881 if got, want := paths[0], "/my/{field}"; got != want { 8882 t.Fatalf("Wrong results path, got %s want %s", got, want) 8883 } 8884 8885 var operation = *result.getPathItemObject("/my/{field}").Post 8886 if got, want := len(operation.Parameters), 2; got != want { 8887 t.Fatalf("Parameters length differed, got %d want %d", got, want) 8888 } 8889 8890 if got, want := operation.Parameters[0].Name, "field"; got != want { 8891 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8892 } 8893 8894 if got, want := operation.Parameters[0].In, "path"; got != want { 8895 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8896 } 8897 8898 if got, want := operation.Parameters[0].Type, "string"; got != want { 8899 t.Fatalf("Wrong parameter type, got %s want %s", got, want) 8900 } 8901 8902 if got, want := operation.Parameters[1].Name, "body"; got != want { 8903 t.Fatalf("Wrong parameter name, got %s want %s", got, want) 8904 } 8905 8906 if got, want := operation.Parameters[1].In, "body"; got != want { 8907 t.Fatalf("Wrong parameter location, got %s want %s", got, want) 8908 } 8909 } 8910 8911 func TestRenderServiceWithHeaderParameters(t *testing.T) { 8912 file := func() descriptor.File { 8913 msgdesc := &descriptorpb.DescriptorProto{ 8914 Name: proto.String("ExampleMessage"), 8915 } 8916 8917 meth := &descriptorpb.MethodDescriptorProto{ 8918 Name: proto.String("Example"), 8919 InputType: proto.String("ExampleMessage"), 8920 OutputType: proto.String("ExampleMessage"), 8921 Options: &descriptorpb.MethodOptions{}, 8922 } 8923 8924 svc := &descriptorpb.ServiceDescriptorProto{ 8925 Name: proto.String("ExampleService"), 8926 Method: []*descriptorpb.MethodDescriptorProto{meth}, 8927 } 8928 8929 msg := &descriptor.Message{ 8930 DescriptorProto: msgdesc, 8931 } 8932 8933 return descriptor.File{ 8934 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 8935 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 8936 Name: proto.String("example.proto"), 8937 Package: proto.String("example"), 8938 MessageType: []*descriptorpb.DescriptorProto{msgdesc}, 8939 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 8940 Options: &descriptorpb.FileOptions{ 8941 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 8942 }, 8943 }, 8944 GoPkg: descriptor.GoPackage{ 8945 Path: "example.com/path/to/example/example.pb", 8946 Name: "example_pb", 8947 }, 8948 Messages: []*descriptor.Message{msg}, 8949 Services: []*descriptor.Service{ 8950 { 8951 ServiceDescriptorProto: svc, 8952 Methods: []*descriptor.Method{ 8953 { 8954 MethodDescriptorProto: meth, 8955 RequestType: msg, 8956 ResponseType: msg, 8957 Bindings: []*descriptor.Binding{ 8958 { 8959 HTTPMethod: "GET", 8960 PathTmpl: httprule.Template{ 8961 Version: 1, 8962 OpCodes: []int{0, 0}, 8963 Template: "/v1/echo", 8964 }, 8965 }, 8966 }, 8967 }, 8968 }, 8969 }, 8970 }, 8971 } 8972 } 8973 8974 type test struct { 8975 file func() descriptor.File 8976 openapiOperation *openapi_options.Operation 8977 parameters openapiParametersObject 8978 } 8979 8980 tests := map[string]*test{ 8981 "type string": { 8982 file: file, 8983 openapiOperation: &openapi_options.Operation{ 8984 Parameters: &openapi_options.Parameters{ 8985 Headers: []*openapi_options.HeaderParameter{ 8986 { 8987 Name: "X-Custom-Header", 8988 Type: openapi_options.HeaderParameter_STRING, 8989 }, 8990 }, 8991 }, 8992 }, 8993 parameters: openapiParametersObject{ 8994 { 8995 Name: "X-Custom-Header", 8996 In: "header", 8997 Type: "string", 8998 }, 8999 }, 9000 }, 9001 "type string with format": { 9002 file: file, 9003 openapiOperation: &openapi_options.Operation{ 9004 Parameters: &openapi_options.Parameters{ 9005 Headers: []*openapi_options.HeaderParameter{ 9006 { 9007 Name: "X-Custom-Header", 9008 Type: openapi_options.HeaderParameter_STRING, 9009 Format: "uuid", 9010 }, 9011 }, 9012 }, 9013 }, 9014 parameters: openapiParametersObject{ 9015 { 9016 Name: "X-Custom-Header", 9017 In: "header", 9018 Type: "string", 9019 Format: "uuid", 9020 }, 9021 }, 9022 }, 9023 "type integer": { 9024 file: file, 9025 openapiOperation: &openapi_options.Operation{ 9026 Parameters: &openapi_options.Parameters{ 9027 Headers: []*openapi_options.HeaderParameter{ 9028 { 9029 Name: "X-Custom-Header", 9030 Type: openapi_options.HeaderParameter_INTEGER, 9031 }, 9032 }, 9033 }, 9034 }, 9035 parameters: openapiParametersObject{ 9036 { 9037 Name: "X-Custom-Header", 9038 In: "header", 9039 Type: "integer", 9040 }, 9041 }, 9042 }, 9043 "type number": { 9044 file: file, 9045 openapiOperation: &openapi_options.Operation{ 9046 Parameters: &openapi_options.Parameters{ 9047 Headers: []*openapi_options.HeaderParameter{ 9048 { 9049 Name: "X-Custom-Header", 9050 Type: openapi_options.HeaderParameter_NUMBER, 9051 }, 9052 }, 9053 }, 9054 }, 9055 parameters: openapiParametersObject{ 9056 { 9057 Name: "X-Custom-Header", 9058 In: "header", 9059 Type: "number", 9060 }, 9061 }, 9062 }, 9063 "type boolean": { 9064 file: file, 9065 openapiOperation: &openapi_options.Operation{ 9066 Parameters: &openapi_options.Parameters{ 9067 Headers: []*openapi_options.HeaderParameter{ 9068 { 9069 Name: "X-Custom-Header", 9070 Type: openapi_options.HeaderParameter_BOOLEAN, 9071 }, 9072 }, 9073 }, 9074 }, 9075 parameters: openapiParametersObject{ 9076 { 9077 Name: "X-Custom-Header", 9078 In: "header", 9079 Type: "boolean", 9080 }, 9081 }, 9082 }, 9083 "header required": { 9084 file: file, 9085 openapiOperation: &openapi_options.Operation{ 9086 Parameters: &openapi_options.Parameters{ 9087 Headers: []*openapi_options.HeaderParameter{ 9088 { 9089 Name: "X-Custom-Header", 9090 Required: true, 9091 Type: openapi_options.HeaderParameter_STRING, 9092 }, 9093 }, 9094 }, 9095 }, 9096 parameters: openapiParametersObject{ 9097 { 9098 Name: "X-Custom-Header", 9099 In: "header", 9100 Required: true, 9101 Type: "string", 9102 }, 9103 }, 9104 }, 9105 } 9106 9107 for name, test := range tests { 9108 test := test 9109 9110 t.Run(name, func(t *testing.T) { 9111 file := test.file() 9112 9113 proto.SetExtension( 9114 proto.Message(file.Services[0].Methods[0].Options), 9115 openapi_options.E_Openapiv2Operation, 9116 test.openapiOperation) 9117 9118 reg := descriptor.NewRegistry() 9119 9120 fileCL := crossLinkFixture(&file) 9121 9122 err := reg.Load(reqFromFile(fileCL)) 9123 if err != nil { 9124 t.Errorf("reg.Load(%#v) failed with %v; want success", file, err) 9125 } 9126 9127 result, err := applyTemplate(param{File: fileCL, reg: reg}) 9128 if err != nil { 9129 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 9130 } 9131 9132 params := result.getPathItemObject("/v1/echo").Get.Parameters 9133 9134 if !reflect.DeepEqual(params, test.parameters) { 9135 t.Errorf("expected %+v, got %+v", test.parameters, params) 9136 } 9137 }) 9138 } 9139 } 9140 9141 func GetPaths(req *openapiSwaggerObject) []string { 9142 paths := make([]string, len(req.Paths)) 9143 i := 0 9144 for _, k := range req.Paths { 9145 paths[i] = k.Path 9146 i++ 9147 } 9148 return paths 9149 } 9150 9151 func TestRenderServicesOpenapiPathsOrderPreserved(t *testing.T) { 9152 reqDesc := &descriptorpb.DescriptorProto{ 9153 Name: proto.String("MyRequest"), 9154 Field: []*descriptorpb.FieldDescriptorProto{ 9155 { 9156 Name: proto.String("field"), 9157 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9158 Number: proto.Int32(1), 9159 }, 9160 }, 9161 } 9162 9163 resDesc := &descriptorpb.DescriptorProto{ 9164 Name: proto.String("MyResponse"), 9165 Field: []*descriptorpb.FieldDescriptorProto{ 9166 { 9167 Name: proto.String("field"), 9168 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9169 Number: proto.Int32(1), 9170 }, 9171 }, 9172 } 9173 meth1 := &descriptorpb.MethodDescriptorProto{ 9174 Name: proto.String("MyMethod1"), 9175 InputType: proto.String("MyRequest"), 9176 OutputType: proto.String("MyResponse"), 9177 } 9178 meth2 := &descriptorpb.MethodDescriptorProto{ 9179 Name: proto.String("MyMethod2"), 9180 InputType: proto.String("MyRequest"), 9181 OutputType: proto.String("MyResponse"), 9182 } 9183 9184 svc := &descriptorpb.ServiceDescriptorProto{ 9185 Name: proto.String("MyService"), 9186 Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2}, 9187 } 9188 reqMsg := &descriptor.Message{ 9189 DescriptorProto: reqDesc, 9190 } 9191 resMsg := &descriptor.Message{ 9192 DescriptorProto: resDesc, 9193 } 9194 reqField := &descriptor.Field{ 9195 Message: reqMsg, 9196 FieldDescriptorProto: reqMsg.GetField()[0], 9197 } 9198 resField := &descriptor.Field{ 9199 Message: resMsg, 9200 FieldDescriptorProto: resMsg.GetField()[0], 9201 } 9202 reqField.JsonName = proto.String("field") 9203 resField.JsonName = proto.String("field") 9204 reqMsg.Fields = []*descriptor.Field{reqField} 9205 resMsg.Fields = []*descriptor.Field{resField} 9206 9207 file := descriptor.File{ 9208 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 9209 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9210 Package: proto.String("example"), 9211 Name: proto.String(",my_service.proto"), 9212 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 9213 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 9214 Options: &descriptorpb.FileOptions{ 9215 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 9216 }, 9217 }, 9218 GoPkg: descriptor.GoPackage{ 9219 Path: "example.com/path/to/example/example.pb", 9220 Name: "example_pb", 9221 }, 9222 Messages: []*descriptor.Message{reqMsg, resMsg}, 9223 Services: []*descriptor.Service{ 9224 { 9225 ServiceDescriptorProto: svc, 9226 Methods: []*descriptor.Method{ 9227 { 9228 MethodDescriptorProto: meth1, 9229 RequestType: reqMsg, 9230 ResponseType: resMsg, 9231 Bindings: []*descriptor.Binding{ 9232 { 9233 HTTPMethod: "POST", 9234 PathTmpl: httprule.Template{ 9235 Version: 1, 9236 OpCodes: []int{0, 0}, 9237 Template: "/c/cpath", 9238 }, 9239 }, 9240 }, 9241 }, { 9242 MethodDescriptorProto: meth2, 9243 RequestType: reqMsg, 9244 ResponseType: resMsg, 9245 Bindings: []*descriptor.Binding{ 9246 { 9247 HTTPMethod: "POST", 9248 PathTmpl: httprule.Template{ 9249 Version: 1, 9250 OpCodes: []int{0, 0}, 9251 Template: "/b/bpath", 9252 }, 9253 }, 9254 }, 9255 }, 9256 }, 9257 }, 9258 }, 9259 } 9260 reg := descriptor.NewRegistry() 9261 reg.SetPreserveRPCOrder(true) 9262 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 9263 if err != nil { 9264 t.Fatalf("failed to reg.Load(): %v", err) 9265 } 9266 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 9267 if err != nil { 9268 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 9269 } 9270 9271 paths := result.Paths 9272 9273 firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template 9274 secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template 9275 for i, pathData := range paths { 9276 switch i { 9277 case 0: 9278 if got, want := pathData.Path, firstRPCPath; got != want { 9279 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9280 } 9281 case 1: 9282 if got, want := pathData.Path, secondRPCPath; got != want { 9283 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9284 } 9285 } 9286 } 9287 } 9288 9289 func TestRenderServicesOpenapiPathsOrderPreservedMultipleServices(t *testing.T) { 9290 reqDesc := &descriptorpb.DescriptorProto{ 9291 Name: proto.String("MyRequest"), 9292 Field: []*descriptorpb.FieldDescriptorProto{ 9293 { 9294 Name: proto.String("field"), 9295 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9296 Number: proto.Int32(1), 9297 }, 9298 }, 9299 } 9300 9301 resDesc := &descriptorpb.DescriptorProto{ 9302 Name: proto.String("MyResponse"), 9303 Field: []*descriptorpb.FieldDescriptorProto{ 9304 { 9305 Name: proto.String("field"), 9306 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9307 Number: proto.Int32(1), 9308 }, 9309 }, 9310 } 9311 meth1 := &descriptorpb.MethodDescriptorProto{ 9312 Name: proto.String("MyMethod1"), 9313 InputType: proto.String("MyRequest"), 9314 OutputType: proto.String("MyResponse"), 9315 } 9316 meth2 := &descriptorpb.MethodDescriptorProto{ 9317 Name: proto.String("MyMethod2"), 9318 InputType: proto.String("MyRequest"), 9319 OutputType: proto.String("MyResponse"), 9320 } 9321 meth3 := &descriptorpb.MethodDescriptorProto{ 9322 Name: proto.String("MyMethod3"), 9323 InputType: proto.String("MyRequest"), 9324 OutputType: proto.String("MyResponse"), 9325 } 9326 meth4 := &descriptorpb.MethodDescriptorProto{ 9327 Name: proto.String("MyMethod4"), 9328 InputType: proto.String("MyRequest"), 9329 OutputType: proto.String("MyResponse"), 9330 } 9331 9332 svc1 := &descriptorpb.ServiceDescriptorProto{ 9333 Name: proto.String("MyServiceOne"), 9334 Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2}, 9335 } 9336 svc2 := &descriptorpb.ServiceDescriptorProto{ 9337 Name: proto.String("MyServiceTwo"), 9338 Method: []*descriptorpb.MethodDescriptorProto{meth3, meth4}, 9339 } 9340 reqMsg := &descriptor.Message{ 9341 DescriptorProto: reqDesc, 9342 } 9343 resMsg := &descriptor.Message{ 9344 DescriptorProto: resDesc, 9345 } 9346 reqField := &descriptor.Field{ 9347 Message: reqMsg, 9348 FieldDescriptorProto: reqMsg.GetField()[0], 9349 } 9350 resField := &descriptor.Field{ 9351 Message: resMsg, 9352 FieldDescriptorProto: resMsg.GetField()[0], 9353 } 9354 reqField.JsonName = proto.String("field") 9355 resField.JsonName = proto.String("field") 9356 reqMsg.Fields = []*descriptor.Field{reqField} 9357 resMsg.Fields = []*descriptor.Field{resField} 9358 9359 file := descriptor.File{ 9360 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 9361 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9362 Package: proto.String("example"), 9363 Name: proto.String(",my_service.proto"), 9364 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 9365 Service: []*descriptorpb.ServiceDescriptorProto{svc1, svc2}, 9366 Options: &descriptorpb.FileOptions{ 9367 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 9368 }, 9369 }, 9370 GoPkg: descriptor.GoPackage{ 9371 Path: "example.com/path/to/example/example.pb", 9372 Name: "example_pb", 9373 }, 9374 Messages: []*descriptor.Message{reqMsg, resMsg}, 9375 Services: []*descriptor.Service{ 9376 { 9377 ServiceDescriptorProto: svc1, 9378 Methods: []*descriptor.Method{ 9379 { 9380 MethodDescriptorProto: meth1, 9381 RequestType: reqMsg, 9382 ResponseType: resMsg, 9383 Bindings: []*descriptor.Binding{ 9384 { 9385 HTTPMethod: "POST", 9386 PathTmpl: httprule.Template{ 9387 Version: 1, 9388 OpCodes: []int{0, 0}, 9389 Template: "/g/gpath", 9390 }, 9391 }, 9392 }, 9393 }, { 9394 MethodDescriptorProto: meth2, 9395 RequestType: reqMsg, 9396 ResponseType: resMsg, 9397 Bindings: []*descriptor.Binding{ 9398 { 9399 HTTPMethod: "POST", 9400 PathTmpl: httprule.Template{ 9401 Version: 1, 9402 OpCodes: []int{0, 0}, 9403 Template: "/f/fpath", 9404 }, 9405 }, 9406 }, 9407 }, 9408 }, 9409 }, { 9410 ServiceDescriptorProto: svc1, 9411 Methods: []*descriptor.Method{ 9412 { 9413 MethodDescriptorProto: meth3, 9414 RequestType: reqMsg, 9415 ResponseType: resMsg, 9416 Bindings: []*descriptor.Binding{ 9417 { 9418 HTTPMethod: "POST", 9419 PathTmpl: httprule.Template{ 9420 Version: 1, 9421 OpCodes: []int{0, 0}, 9422 Template: "/c/cpath", 9423 }, 9424 }, 9425 }, 9426 }, { 9427 MethodDescriptorProto: meth4, 9428 RequestType: reqMsg, 9429 ResponseType: resMsg, 9430 Bindings: []*descriptor.Binding{ 9431 { 9432 HTTPMethod: "POST", 9433 PathTmpl: httprule.Template{ 9434 Version: 1, 9435 OpCodes: []int{0, 0}, 9436 Template: "/b/bpath", 9437 }, 9438 }, 9439 }, 9440 }, 9441 }, 9442 }, 9443 }, 9444 } 9445 reg := descriptor.NewRegistry() 9446 reg.SetPreserveRPCOrder(true) 9447 reg.SetUseJSONNamesForFields(true) 9448 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 9449 if err != nil { 9450 t.Fatalf("failed to reg.Load(): %v", err) 9451 } 9452 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 9453 if err != nil { 9454 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 9455 } 9456 9457 paths := result.Paths 9458 9459 firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template 9460 secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template 9461 thirdRPCPath := file.Services[1].Methods[0].Bindings[0].PathTmpl.Template 9462 fourthRPCPath := file.Services[1].Methods[1].Bindings[0].PathTmpl.Template 9463 for i, pathData := range paths { 9464 switch i { 9465 case 0: 9466 if got, want := pathData.Path, firstRPCPath; got != want { 9467 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9468 } 9469 case 1: 9470 if got, want := pathData.Path, secondRPCPath; got != want { 9471 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9472 } 9473 case 2: 9474 if got, want := pathData.Path, thirdRPCPath; got != want { 9475 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9476 } 9477 case 3: 9478 if got, want := pathData.Path, fourthRPCPath; got != want { 9479 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9480 } 9481 } 9482 } 9483 } 9484 9485 func TestRenderServicesOpenapiPathsOrderPreservedAdditionalBindings(t *testing.T) { 9486 reqDesc := &descriptorpb.DescriptorProto{ 9487 Name: proto.String("MyRequest"), 9488 Field: []*descriptorpb.FieldDescriptorProto{ 9489 { 9490 Name: proto.String("field"), 9491 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9492 Number: proto.Int32(1), 9493 }, 9494 }, 9495 } 9496 9497 resDesc := &descriptorpb.DescriptorProto{ 9498 Name: proto.String("MyResponse"), 9499 Field: []*descriptorpb.FieldDescriptorProto{ 9500 { 9501 Name: proto.String("field"), 9502 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9503 Number: proto.Int32(1), 9504 }, 9505 }, 9506 } 9507 meth1 := &descriptorpb.MethodDescriptorProto{ 9508 Name: proto.String("MyMethod1"), 9509 InputType: proto.String("MyRequest"), 9510 OutputType: proto.String("MyResponse"), 9511 } 9512 meth2 := &descriptorpb.MethodDescriptorProto{ 9513 Name: proto.String("MyMethod2"), 9514 InputType: proto.String("MyRequest"), 9515 OutputType: proto.String("MyResponse"), 9516 } 9517 9518 svc := &descriptorpb.ServiceDescriptorProto{ 9519 Name: proto.String("MyService"), 9520 Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2}, 9521 } 9522 reqMsg := &descriptor.Message{ 9523 DescriptorProto: reqDesc, 9524 } 9525 resMsg := &descriptor.Message{ 9526 DescriptorProto: resDesc, 9527 } 9528 reqField := &descriptor.Field{ 9529 Message: reqMsg, 9530 FieldDescriptorProto: reqMsg.GetField()[0], 9531 } 9532 resField := &descriptor.Field{ 9533 Message: resMsg, 9534 FieldDescriptorProto: resMsg.GetField()[0], 9535 } 9536 reqField.JsonName = proto.String("field") 9537 resField.JsonName = proto.String("field") 9538 reqMsg.Fields = []*descriptor.Field{reqField} 9539 resMsg.Fields = []*descriptor.Field{resField} 9540 9541 file := descriptor.File{ 9542 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 9543 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9544 Package: proto.String("example"), 9545 Name: proto.String(",my_service.proto"), 9546 MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc}, 9547 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 9548 Options: &descriptorpb.FileOptions{ 9549 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 9550 }, 9551 }, 9552 GoPkg: descriptor.GoPackage{ 9553 Path: "example.com/path/to/example/example.pb", 9554 Name: "example_pb", 9555 }, 9556 Messages: []*descriptor.Message{reqMsg, resMsg}, 9557 Services: []*descriptor.Service{ 9558 { 9559 ServiceDescriptorProto: svc, 9560 Methods: []*descriptor.Method{ 9561 { 9562 MethodDescriptorProto: meth1, 9563 RequestType: reqMsg, 9564 ResponseType: resMsg, 9565 Bindings: []*descriptor.Binding{ 9566 { 9567 HTTPMethod: "POST", 9568 PathTmpl: httprule.Template{ 9569 Version: 1, 9570 OpCodes: []int{0, 0}, 9571 Template: "/c/cpath", 9572 }, 9573 }, { 9574 HTTPMethod: "GET", 9575 PathTmpl: httprule.Template{ 9576 Version: 1, 9577 OpCodes: []int{0, 0}, 9578 Template: "/additionalbinding", 9579 }, 9580 }, 9581 }, 9582 }, { 9583 MethodDescriptorProto: meth2, 9584 RequestType: reqMsg, 9585 ResponseType: resMsg, 9586 Bindings: []*descriptor.Binding{ 9587 { 9588 HTTPMethod: "POST", 9589 PathTmpl: httprule.Template{ 9590 Version: 1, 9591 OpCodes: []int{0, 0}, 9592 Template: "/b/bpath", 9593 }, 9594 }, 9595 }, 9596 }, 9597 }, 9598 }, 9599 }, 9600 } 9601 reg := descriptor.NewRegistry() 9602 reg.SetPreserveRPCOrder(true) 9603 reg.SetUseJSONNamesForFields(true) 9604 err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}}) 9605 if err != nil { 9606 t.Fatalf("failed to reg.Load(): %v", err) 9607 } 9608 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 9609 if err != nil { 9610 t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err) 9611 } 9612 9613 paths := result.Paths 9614 if err != nil { 9615 t.Fatalf("failed to obtain extension paths: %v", err) 9616 } 9617 9618 firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template 9619 firstRPCPathAdditionalBinding := file.Services[0].Methods[0].Bindings[1].PathTmpl.Template 9620 secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template 9621 for i, pathData := range paths { 9622 switch i { 9623 case 0: 9624 if got, want := pathData.Path, firstRPCPath; got != want { 9625 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9626 } 9627 case 1: 9628 if got, want := pathData.Path, firstRPCPathAdditionalBinding; got != want { 9629 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9630 } 9631 case 2: 9632 if got, want := pathData.Path, secondRPCPath; got != want { 9633 t.Fatalf("RPC path order not preserved, got %s want %s", got, want) 9634 } 9635 } 9636 } 9637 } 9638 9639 func TestArrayMessageItemsType(t *testing.T) { 9640 9641 msgDesc := &descriptorpb.DescriptorProto{ 9642 Name: proto.String("ExampleMessage"), 9643 Field: []*descriptorpb.FieldDescriptorProto{ 9644 9645 { 9646 Name: proto.String("children"), 9647 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 9648 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 9649 TypeName: proto.String(".example.ExampleMessage"), 9650 Number: proto.Int32(1), 9651 JsonName: proto.String("children"), 9652 }, 9653 }, 9654 } 9655 9656 nestDesc := &descriptorpb.DescriptorProto{ 9657 Name: proto.String("NestDescMessage"), 9658 Field: []*descriptorpb.FieldDescriptorProto{ 9659 { 9660 Name: proto.String("children"), 9661 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 9662 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 9663 TypeName: proto.String(".example.ExampleMessage"), 9664 Number: proto.Int32(1), 9665 JsonName: proto.String("children"), 9666 }, 9667 }, 9668 } 9669 9670 meth := &descriptorpb.MethodDescriptorProto{ 9671 Name: proto.String("Example"), 9672 InputType: proto.String("ExampleMessage"), 9673 OutputType: proto.String("NestDescMessage"), 9674 } 9675 svc := &descriptorpb.ServiceDescriptorProto{ 9676 Name: proto.String("ExampleService"), 9677 Method: []*descriptorpb.MethodDescriptorProto{meth}, 9678 } 9679 msg := &descriptor.Message{ 9680 DescriptorProto: msgDesc, 9681 } 9682 nsg := &descriptor.Message{ 9683 DescriptorProto: nestDesc, 9684 } 9685 msg.Fields = []*descriptor.Field{ 9686 { 9687 Message: msg, 9688 FieldDescriptorProto: msg.GetField()[0], 9689 }, 9690 } 9691 nsg.Fields = []*descriptor.Field{ 9692 { 9693 Message: nsg, 9694 FieldDescriptorProto: nsg.GetField()[0], 9695 }, 9696 } 9697 file := descriptor.File{ 9698 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 9699 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9700 Name: proto.String("example.proto"), 9701 Package: proto.String("example"), 9702 MessageType: []*descriptorpb.DescriptorProto{msgDesc, nestDesc}, 9703 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 9704 Options: &descriptorpb.FileOptions{ 9705 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 9706 }, 9707 }, 9708 GoPkg: descriptor.GoPackage{ 9709 Path: "example.com/path/to/example/example.pb", 9710 Name: "example_pb", 9711 }, 9712 Messages: []*descriptor.Message{msg, nsg}, 9713 Services: []*descriptor.Service{ 9714 { 9715 ServiceDescriptorProto: svc, 9716 Methods: []*descriptor.Method{ 9717 { 9718 MethodDescriptorProto: meth, 9719 RequestType: msg, 9720 ResponseType: nsg, 9721 Bindings: []*descriptor.Binding{ 9722 { 9723 HTTPMethod: "POST", 9724 Body: &descriptor.Body{ 9725 FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}), 9726 }, 9727 PathTmpl: httprule.Template{ 9728 Version: 1, 9729 OpCodes: []int{0, 0}, 9730 Template: "/v1/echo", // TODO(achew22): Figure out what this should really be 9731 }, 9732 }, 9733 }, 9734 }, 9735 }, 9736 }, 9737 }, 9738 } 9739 reg := descriptor.NewRegistry() 9740 reg.SetUseJSONNamesForFields(true) 9741 if err := AddErrorDefs(reg); err != nil { 9742 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 9743 return 9744 } 9745 fileCL := crossLinkFixture(&file) 9746 if err := reg.Load(&pluginpb.CodeGeneratorRequest{ 9747 ProtoFile: []*descriptorpb.FileDescriptorProto{ 9748 { 9749 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9750 Name: proto.String("acme/example.proto"), 9751 Package: proto.String("example"), 9752 MessageType: []*descriptorpb.DescriptorProto{msgDesc, nestDesc}, 9753 Service: []*descriptorpb.ServiceDescriptorProto{}, 9754 Options: &descriptorpb.FileOptions{ 9755 GoPackage: proto.String("acme/example"), 9756 }, 9757 }, 9758 }, 9759 }); err != nil { 9760 t.Errorf("reg.Load(%#v) failed with %v; want success", reg, err) 9761 return 9762 } 9763 expect := openapiDefinitionsObject{ 9764 "rpcStatus": openapiSchemaObject{ 9765 schemaCore: schemaCore{ 9766 Type: "object", 9767 }, 9768 Properties: &openapiSchemaObjectProperties{ 9769 keyVal{ 9770 Key: "code", 9771 Value: openapiSchemaObject{ 9772 schemaCore: schemaCore{ 9773 Type: "integer", 9774 Format: "int32", 9775 }, 9776 }, 9777 }, 9778 keyVal{ 9779 Key: "message", 9780 Value: openapiSchemaObject{ 9781 schemaCore: schemaCore{ 9782 Type: "string", 9783 }, 9784 }, 9785 }, 9786 keyVal{ 9787 Key: "details", 9788 Value: openapiSchemaObject{ 9789 schemaCore: schemaCore{ 9790 Type: "array", 9791 Items: &openapiItemsObject{ 9792 schemaCore: schemaCore{ 9793 Type: "object", 9794 Ref: "#/definitions/protobufAny", 9795 }, 9796 }, 9797 }, 9798 }, 9799 }, 9800 }, 9801 }, 9802 "exampleExampleMessage": openapiSchemaObject{ 9803 schemaCore: schemaCore{ 9804 Type: "object", 9805 }, 9806 Properties: &openapiSchemaObjectProperties{ 9807 keyVal{ 9808 Key: "children", 9809 Value: openapiSchemaObject{ 9810 schemaCore: schemaCore{ 9811 Type: "array", 9812 Items: &openapiItemsObject{ 9813 schemaCore: schemaCore{ 9814 Type: "object", 9815 Ref: "#/definitions/exampleExampleMessage", 9816 }, 9817 }, 9818 }, 9819 }, 9820 }, 9821 }, 9822 }, 9823 "exampleNestDescMessage": openapiSchemaObject{ 9824 schemaCore: schemaCore{ 9825 Type: "object", 9826 }, 9827 Properties: &openapiSchemaObjectProperties{ 9828 keyVal{ 9829 Key: "children", 9830 Value: openapiSchemaObject{ 9831 schemaCore: schemaCore{ 9832 Type: "array", 9833 Items: &openapiItemsObject{ 9834 schemaCore: schemaCore{ 9835 Type: "object", 9836 Ref: "#/definitions/exampleExampleMessage", 9837 }, 9838 }, 9839 }, 9840 }, 9841 }, 9842 }, 9843 }, 9844 "protobufAny": openapiSchemaObject{ 9845 schemaCore: schemaCore{ 9846 Type: "object", 9847 }, 9848 Properties: &openapiSchemaObjectProperties{ 9849 keyVal{ 9850 Key: "@type", 9851 Value: openapiSchemaObject{ 9852 schemaCore: schemaCore{ 9853 Type: "string", 9854 }, 9855 }, 9856 }, 9857 }, 9858 AdditionalProperties: &openapiSchemaObject{}, 9859 }, 9860 } 9861 9862 result, err := applyTemplate(param{File: fileCL, reg: reg}) 9863 if err != nil { 9864 t.Errorf("applyTemplate(%#v) failed with %v; want success", reg, err) 9865 return 9866 } 9867 if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) { 9868 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 9869 } 9870 if want, is, name := expect, result.Definitions, "Produces"; !reflect.DeepEqual(is, want) { 9871 9872 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, is, want) 9873 } 9874 // If there was a failure, print out the input and the json result for debugging. 9875 if t.Failed() { 9876 t.Errorf("had: %s", file) 9877 t.Errorf("got: %s", fmt.Sprint(result)) 9878 } 9879 } 9880 9881 func TestQueryParameterType(t *testing.T) { 9882 ntDesc := &descriptorpb.DescriptorProto{ 9883 Name: proto.String("AddressEntry"), 9884 Field: []*descriptorpb.FieldDescriptorProto{ 9885 { 9886 Name: proto.String("key"), 9887 Number: proto.Int32(1), 9888 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 9889 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9890 JsonName: proto.String("key"), 9891 }, 9892 { 9893 Name: proto.String("value"), 9894 Number: proto.Int32(2), 9895 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 9896 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 9897 JsonName: proto.String("value"), 9898 }, 9899 }, 9900 Options: &descriptorpb.MessageOptions{ 9901 MapEntry: proto.Bool(true), 9902 }, 9903 } 9904 9905 msgDesc := &descriptorpb.DescriptorProto{ 9906 Name: proto.String("Person"), 9907 Field: []*descriptorpb.FieldDescriptorProto{ 9908 { 9909 Name: proto.String("Address"), 9910 Number: proto.Int32(1), 9911 Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(), 9912 Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), 9913 TypeName: proto.String(".example.com.Person.AddressEntry"), 9914 JsonName: proto.String("Address"), 9915 }, 9916 }, 9917 NestedType: []*descriptorpb.DescriptorProto{ 9918 ntDesc, 9919 }, 9920 } 9921 9922 nesteDesc := &descriptorpb.DescriptorProto{ 9923 Name: proto.String("ExampleResponse"), 9924 Field: []*descriptorpb.FieldDescriptorProto{ 9925 { 9926 Name: proto.String("Key"), 9927 Number: proto.Int32(1), 9928 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 9929 Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(), 9930 JsonName: proto.String("Key"), 9931 }, 9932 { 9933 Name: proto.String("Value"), 9934 Number: proto.Int32(2), 9935 Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), 9936 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), 9937 JsonName: proto.String("Value"), 9938 }, 9939 }, 9940 } 9941 9942 meth := &descriptorpb.MethodDescriptorProto{ 9943 Name: proto.String("Example"), 9944 InputType: proto.String("Person"), 9945 OutputType: proto.String("ExampleResponse"), 9946 } 9947 svc := &descriptorpb.ServiceDescriptorProto{ 9948 Name: proto.String("ExampleService"), 9949 Method: []*descriptorpb.MethodDescriptorProto{meth}, 9950 } 9951 msg := &descriptor.Message{ 9952 DescriptorProto: msgDesc, 9953 } 9954 nt := &descriptor.Message{ 9955 DescriptorProto: ntDesc, 9956 } 9957 nest := &descriptor.Message{ 9958 DescriptorProto: nesteDesc, 9959 } 9960 msg.Fields = []*descriptor.Field{ 9961 { 9962 Message: msg, 9963 FieldDescriptorProto: msg.GetField()[0], 9964 }, 9965 } 9966 nt.Fields = []*descriptor.Field{ 9967 { 9968 Message: nt, 9969 FieldDescriptorProto: msg.GetField()[0], 9970 }, 9971 } 9972 nest.Fields = []*descriptor.Field{ 9973 { 9974 Message: nest, 9975 FieldDescriptorProto: nest.GetField()[0], 9976 }, 9977 { 9978 Message: nest, 9979 FieldDescriptorProto: nest.GetField()[1], 9980 }, 9981 } 9982 file := descriptor.File{ 9983 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 9984 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 9985 Name: proto.String("person.proto"), 9986 Package: proto.String("example.com"), 9987 MessageType: []*descriptorpb.DescriptorProto{msgDesc, nesteDesc}, 9988 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 9989 Options: &descriptorpb.FileOptions{ 9990 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 9991 }, 9992 }, 9993 GoPkg: descriptor.GoPackage{ 9994 Path: "example.com/path/to/example/example.pb", 9995 Name: "example_pb", 9996 }, 9997 Messages: []*descriptor.Message{msg, nest}, 9998 Services: []*descriptor.Service{ 9999 { 10000 ServiceDescriptorProto: svc, 10001 Methods: []*descriptor.Method{ 10002 { 10003 MethodDescriptorProto: meth, 10004 RequestType: msg, 10005 ResponseType: nest, 10006 Bindings: []*descriptor.Binding{ 10007 { 10008 HTTPMethod: "GET", 10009 PathTmpl: httprule.Template{ 10010 Version: 1, 10011 OpCodes: []int{0, 0}, 10012 Template: "/v1/echo", 10013 }, 10014 }, 10015 }, 10016 }, 10017 }, 10018 }, 10019 }, 10020 } 10021 expect := openapiPathsObject{{ 10022 Path: "/v1/echo", 10023 PathItemObject: openapiPathItemObject{ 10024 Get: &openapiOperationObject{ 10025 Parameters: openapiParametersObject{ 10026 { 10027 Name: "Address[string]", 10028 Description: `This is a request variable of the map type. The query format is "map_name[key]=value", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age["bob"]=18`, 10029 In: "query", 10030 Type: "integer", 10031 }, 10032 }, 10033 }, 10034 }, 10035 }} 10036 10037 reg := descriptor.NewRegistry() 10038 reg.SetUseJSONNamesForFields(false) 10039 if err := AddErrorDefs(reg); err != nil { 10040 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 10041 return 10042 } 10043 fileCL := crossLinkFixture(&file) 10044 err := reg.Load(&pluginpb.CodeGeneratorRequest{ 10045 ProtoFile: []*descriptorpb.FileDescriptorProto{ 10046 { 10047 Name: proto.String("person.proto"), 10048 Package: proto.String("example.com"), 10049 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 10050 MessageType: []*descriptorpb.DescriptorProto{msgDesc, nesteDesc}, 10051 Service: []*descriptorpb.ServiceDescriptorProto{}, 10052 Options: &descriptorpb.FileOptions{ 10053 GoPackage: proto.String("person.proto"), 10054 }, 10055 }, 10056 }, 10057 }) 10058 if err != nil { 10059 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 10060 return 10061 } 10062 result, err := applyTemplate(param{File: fileCL, reg: reg}) 10063 if err != nil { 10064 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 10065 return 10066 } 10067 if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) { 10068 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want) 10069 } 10070 10071 if want, is, name := expect[0].PathItemObject.Get.Parameters, result.getPathItemObject("/v1/echo").Get.Parameters, "Produces"; !reflect.DeepEqual(is, want) { 10072 10073 t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, is, want) 10074 } 10075 // If there was a failure, print out the input and the json result for debugging. 10076 if t.Failed() { 10077 t.Errorf("had: %s", file) 10078 t.Errorf("got: %s", fmt.Sprint(result)) 10079 } 10080 } 10081 10082 func TestApplyTemplateRequestWithServerStreamingHttpBody(t *testing.T) { 10083 meth := &descriptorpb.MethodDescriptorProto{ 10084 Name: proto.String("Echo"), 10085 InputType: proto.String(".google.api.HttpBody"), 10086 OutputType: proto.String(".google.api.HttpBody"), 10087 ClientStreaming: proto.Bool(false), 10088 ServerStreaming: proto.Bool(true), 10089 } 10090 svc := &descriptorpb.ServiceDescriptorProto{ 10091 Name: proto.String("ExampleService"), 10092 Method: []*descriptorpb.MethodDescriptorProto{meth}, 10093 } 10094 httpBodyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/api/httpbody.proto") 10095 if err != nil { 10096 t.Fatal(err) 10097 } 10098 httpBodyFile.SourceLocations() 10099 desc, err := protoregistry.GlobalFiles.FindDescriptorByName("google.api.HttpBody") 10100 if err != nil { 10101 t.Fatal(err) 10102 } 10103 msg := &descriptor.Message{ 10104 DescriptorProto: protodesc.ToDescriptorProto(desc.(protoreflect.MessageDescriptor)), 10105 File: &descriptor.File{ 10106 FileDescriptorProto: protodesc.ToFileDescriptorProto(httpBodyFile), 10107 }, 10108 } 10109 anyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/protobuf/any.proto") 10110 if err != nil { 10111 t.Fatal(err) 10112 } 10113 file := descriptor.File{ 10114 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 10115 SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, 10116 Name: proto.String("example.proto"), 10117 Package: proto.String("example"), 10118 Dependency: []string{ 10119 "google/api/httpbody.proto", 10120 }, 10121 Service: []*descriptorpb.ServiceDescriptorProto{svc}, 10122 Options: &descriptorpb.FileOptions{ 10123 GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), 10124 }, 10125 }, 10126 GoPkg: descriptor.GoPackage{ 10127 Path: "example.com/path/to/example/example.pb", 10128 Name: "example_pb", 10129 }, 10130 Services: []*descriptor.Service{ 10131 { 10132 ServiceDescriptorProto: svc, 10133 Methods: []*descriptor.Method{ 10134 { 10135 MethodDescriptorProto: meth, 10136 RequestType: msg, 10137 ResponseType: msg, 10138 Bindings: []*descriptor.Binding{ 10139 { 10140 HTTPMethod: "POST", 10141 PathTmpl: httprule.Template{ 10142 Version: 1, 10143 OpCodes: []int{0, 0}, 10144 Template: "/v1/echo", 10145 }, 10146 }, 10147 }, 10148 }, 10149 }, 10150 }, 10151 }, 10152 } 10153 reg := descriptor.NewRegistry() 10154 if err := AddErrorDefs(reg); err != nil { 10155 t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) 10156 return 10157 } 10158 err = reg.Load(&pluginpb.CodeGeneratorRequest{ 10159 ProtoFile: []*descriptorpb.FileDescriptorProto{ 10160 protodesc.ToFileDescriptorProto(anyFile), 10161 protodesc.ToFileDescriptorProto(httpBodyFile), 10162 file.FileDescriptorProto, 10163 }, 10164 }) 10165 if err != nil { 10166 t.Fatalf("failed to load code generator request: %v", err) 10167 } 10168 result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) 10169 if err != nil { 10170 t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) 10171 return 10172 } 10173 10174 if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { 10175 t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) 10176 } 10177 10178 if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok { 10179 t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`) 10180 } else { 10181 if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) { 10182 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 10183 } 10184 streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema 10185 if want, got, name := "string", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) { 10186 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 10187 } 10188 if want, got, name := "binary", streamExampleExampleMessage.Format, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Format`; !reflect.DeepEqual(got, want) { 10189 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 10190 } 10191 if want, got, name := "Free form byte stream", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) { 10192 t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) 10193 } 10194 if len(*streamExampleExampleMessage.Properties) != 0 { 10195 t.Errorf("applyTemplate(%#v).Properties should be empty", file) 10196 } 10197 } 10198 10199 // If there was a failure, print out the input and the json result for debugging. 10200 if t.Failed() { 10201 t.Errorf("had: %s", file) 10202 t.Errorf("got: %s", fmt.Sprint(result)) 10203 } 10204 } 10205 10206 // Returns the openapiPathItemObject associated with a path. 10207 func (so openapiSwaggerObject) getPathItemObject(path string) openapiPathItemObject { 10208 for _, pathData := range so.Paths { 10209 if pathData.Path == path { 10210 return pathData.PathItemObject 10211 } 10212 } 10213 10214 return openapiPathItemObject{} 10215 } 10216 10217 func TestGetPathItemObjectSwaggerObjectMethod(t *testing.T) { 10218 testCases := [...]struct { 10219 testName string 10220 swaggerObject openapiSwaggerObject 10221 path string 10222 expectedPathItemObject openapiPathItemObject 10223 }{ 10224 { 10225 testName: "Path present in swagger object", 10226 swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{ 10227 Path: "a/path", 10228 PathItemObject: openapiPathItemObject{ 10229 Get: &openapiOperationObject{ 10230 Description: "A testful description", 10231 }, 10232 }, 10233 }}}, 10234 path: "a/path", 10235 expectedPathItemObject: openapiPathItemObject{ 10236 Get: &openapiOperationObject{ 10237 Description: "A testful description", 10238 }, 10239 }, 10240 }, { 10241 testName: "Path not present in swaggerObject", 10242 swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{ 10243 Path: "a/path", 10244 PathItemObject: openapiPathItemObject{ 10245 Get: &openapiOperationObject{ 10246 Description: "A testful description", 10247 }, 10248 }, 10249 }}}, 10250 path: "b/path", 10251 expectedPathItemObject: openapiPathItemObject{}, 10252 }, { 10253 testName: "Path present in swaggerPathsObject with multiple paths", 10254 swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{ 10255 Path: "a/path", 10256 PathItemObject: openapiPathItemObject{ 10257 Get: &openapiOperationObject{ 10258 Description: "A testful description", 10259 }, 10260 }, 10261 }, { 10262 Path: "another/path", 10263 PathItemObject: openapiPathItemObject{ 10264 Get: &openapiOperationObject{ 10265 Description: "Another testful description", 10266 }, 10267 }, 10268 }}}, 10269 path: "another/path", 10270 expectedPathItemObject: openapiPathItemObject{ 10271 Get: &openapiOperationObject{ 10272 Description: "Another testful description", 10273 }, 10274 }, 10275 }, { 10276 testName: "Path not present in swaggerObject with no paths", 10277 swaggerObject: openapiSwaggerObject{}, 10278 path: "b/path", 10279 expectedPathItemObject: openapiPathItemObject{}, 10280 }, 10281 } 10282 10283 for _, tc := range testCases { 10284 tc := tc 10285 10286 t.Run(tc.testName, func(t *testing.T) { 10287 actualPathItemObject := tc.swaggerObject.getPathItemObject(tc.path) 10288 if isEqual := reflect.DeepEqual(actualPathItemObject, tc.expectedPathItemObject); !isEqual { 10289 t.Fatalf("Got pathItemObject: %#v, want pathItemObject: %#v", actualPathItemObject, tc.expectedPathItemObject) 10290 } 10291 }) 10292 } 10293 } 10294 10295 func TestGetPathItemObjectFunction(t *testing.T) { 10296 testCases := [...]struct { 10297 testName string 10298 paths openapiPathsObject 10299 path string 10300 expectedPathItemObject openapiPathItemObject 10301 expectedIsPathPresent bool 10302 }{ 10303 { 10304 testName: "Path present in openapiPathsObject", 10305 paths: openapiPathsObject{{ 10306 Path: "a/path", 10307 PathItemObject: openapiPathItemObject{ 10308 Get: &openapiOperationObject{ 10309 Description: "A testful description", 10310 }, 10311 }, 10312 }}, 10313 path: "a/path", 10314 expectedPathItemObject: openapiPathItemObject{ 10315 Get: &openapiOperationObject{ 10316 Description: "A testful description", 10317 }, 10318 }, 10319 expectedIsPathPresent: true, 10320 }, { 10321 testName: "Path not present in openapiPathsObject", 10322 paths: openapiPathsObject{{ 10323 Path: "a/path", 10324 PathItemObject: openapiPathItemObject{ 10325 Get: &openapiOperationObject{ 10326 Description: "A testful description", 10327 }, 10328 }, 10329 }}, 10330 path: "b/path", 10331 expectedPathItemObject: openapiPathItemObject{}, 10332 expectedIsPathPresent: false, 10333 }, { 10334 testName: "Path present in openapiPathsObject with multiple paths", 10335 paths: openapiPathsObject{{ 10336 Path: "a/path", 10337 PathItemObject: openapiPathItemObject{ 10338 Get: &openapiOperationObject{ 10339 Description: "A testful description", 10340 }, 10341 }, 10342 }, { 10343 Path: "another/path", 10344 PathItemObject: openapiPathItemObject{ 10345 Get: &openapiOperationObject{ 10346 Description: "Another testful description", 10347 }, 10348 }, 10349 }}, 10350 path: "another/path", 10351 expectedPathItemObject: openapiPathItemObject{ 10352 Get: &openapiOperationObject{ 10353 Description: "Another testful description", 10354 }, 10355 }, 10356 expectedIsPathPresent: true, 10357 }, { 10358 testName: "Path not present in empty openapiPathsObject", 10359 paths: openapiPathsObject{}, 10360 path: "b/path", 10361 expectedPathItemObject: openapiPathItemObject{}, 10362 expectedIsPathPresent: false, 10363 }, 10364 } 10365 10366 for _, tc := range testCases { 10367 tc := tc 10368 10369 t.Run(tc.testName, func(t *testing.T) { 10370 actualPathItemObject, actualIsPathPresent := getPathItemObject(tc.paths, tc.path) 10371 if isEqual := reflect.DeepEqual(actualPathItemObject, tc.expectedPathItemObject); !isEqual { 10372 t.Fatalf("Got pathItemObject: %#v, want pathItemObject: %#v", actualPathItemObject, tc.expectedPathItemObject) 10373 } 10374 if actualIsPathPresent != tc.expectedIsPathPresent { 10375 t.Fatalf("Got isPathPresent bool: %t, want isPathPresent bool: %t", actualIsPathPresent, tc.expectedIsPathPresent) 10376 } 10377 }) 10378 } 10379 } 10380 10381 func TestUpdatePaths(t *testing.T) { 10382 testCases := [...]struct { 10383 testName string 10384 paths openapiPathsObject 10385 pathToUpdate string 10386 newPathItemObject openapiPathItemObject 10387 expectedUpdatedPaths openapiPathsObject 10388 }{ 10389 { 10390 testName: "Path present in openapiPathsObject, pathItemObject updated.", 10391 paths: openapiPathsObject{{ 10392 Path: "a/path", 10393 PathItemObject: openapiPathItemObject{ 10394 Get: &openapiOperationObject{ 10395 Description: "A testful description", 10396 }, 10397 }, 10398 }}, 10399 pathToUpdate: "a/path", 10400 newPathItemObject: openapiPathItemObject{ 10401 Get: &openapiOperationObject{ 10402 Description: "A newly updated testful description", 10403 }, 10404 }, 10405 expectedUpdatedPaths: openapiPathsObject{{ 10406 Path: "a/path", 10407 PathItemObject: openapiPathItemObject{ 10408 Get: &openapiOperationObject{ 10409 Description: "A newly updated testful description", 10410 }, 10411 }, 10412 }}, 10413 }, { 10414 testName: "Path not present in openapiPathsObject, new path data appended.", 10415 paths: openapiPathsObject{{ 10416 Path: "c/path", 10417 PathItemObject: openapiPathItemObject{ 10418 Get: &openapiOperationObject{ 10419 Description: "A testful description", 10420 }, 10421 }, 10422 }}, 10423 pathToUpdate: "b/path", 10424 newPathItemObject: openapiPathItemObject{ 10425 Get: &openapiOperationObject{ 10426 Description: "A new testful description to add", 10427 }, 10428 }, 10429 expectedUpdatedPaths: openapiPathsObject{{ 10430 Path: "c/path", 10431 PathItemObject: openapiPathItemObject{ 10432 Get: &openapiOperationObject{ 10433 Description: "A testful description", 10434 }, 10435 }, 10436 }, { 10437 Path: "b/path", 10438 PathItemObject: openapiPathItemObject{ 10439 Get: &openapiOperationObject{ 10440 Description: "A new testful description to add", 10441 }, 10442 }, 10443 }}, 10444 }, { 10445 testName: "No paths present in openapiPathsObject, new path data appended.", 10446 paths: openapiPathsObject{}, 10447 pathToUpdate: "b/path", 10448 newPathItemObject: openapiPathItemObject{ 10449 Get: &openapiOperationObject{ 10450 Description: "A new testful description to add", 10451 }, 10452 }, 10453 expectedUpdatedPaths: openapiPathsObject{{ 10454 Path: "b/path", 10455 PathItemObject: openapiPathItemObject{ 10456 Get: &openapiOperationObject{ 10457 Description: "A new testful description to add", 10458 }, 10459 }, 10460 }}, 10461 }, 10462 } 10463 10464 for _, tc := range testCases { 10465 tc := tc 10466 10467 t.Run(tc.testName, func(t *testing.T) { 10468 updatePaths(&tc.paths, tc.pathToUpdate, tc.newPathItemObject) 10469 if pathsCorrectlyUpdated := reflect.DeepEqual(tc.paths, tc.expectedUpdatedPaths); !pathsCorrectlyUpdated { 10470 t.Fatalf("Paths not correctly updated. Want %#v, got %#v", tc.expectedUpdatedPaths, tc.paths) 10471 } 10472 }) 10473 } 10474 } 10475 10476 // Test that enum values have internal comments removed 10477 func TestEnumValueProtoComments(t *testing.T) { 10478 reg := descriptor.NewRegistry() 10479 name := "kind" 10480 comments := "(-- this is a comment --)" 10481 10482 enum := &descriptor.Enum{ 10483 EnumDescriptorProto: &descriptorpb.EnumDescriptorProto{ 10484 Name: &name, 10485 }, 10486 File: &descriptor.File{ 10487 FileDescriptorProto: &descriptorpb.FileDescriptorProto{ 10488 Name: new(string), 10489 Package: new(string), 10490 SourceCodeInfo: &descriptorpb.SourceCodeInfo{ 10491 Location: []*descriptorpb.SourceCodeInfo_Location{ 10492 &descriptorpb.SourceCodeInfo_Location{ 10493 LeadingComments: &comments, 10494 }, 10495 }, 10496 }, 10497 }, 10498 }, 10499 } 10500 comments = enumValueProtoComments(reg, enum) 10501 if comments != "" { 10502 t.Errorf("expected '', got '%v'", comments) 10503 } 10504 } 10505 10506 func MustMarshal(v interface{}) []byte { 10507 b, err := json.Marshal(v) 10508 if err != nil { 10509 panic(err) 10510 } 10511 return b 10512 } 10513 10514 func TestMergeTags(t *testing.T) { 10515 testCases := [...]struct { 10516 testName string 10517 existingTags []openapiTagObject 10518 newTags []openapiTagObject 10519 expectedMergedTags []openapiTagObject 10520 }{ 10521 { 10522 testName: "Simple merge.", 10523 existingTags: []openapiTagObject{{ 10524 Name: "tag1", 10525 Description: "tag1 description", 10526 }}, 10527 newTags: []openapiTagObject{{ 10528 Name: "tag2", 10529 Description: "tag2 description", 10530 }}, 10531 expectedMergedTags: []openapiTagObject{{ 10532 Name: "tag1", 10533 Description: "tag1 description", 10534 }, { 10535 Name: "tag2", 10536 Description: "tag2 description", 10537 }}, 10538 }, 10539 { 10540 testName: "Merge description", 10541 existingTags: []openapiTagObject{{ 10542 Name: "tag1", 10543 Description: "tag1 description", 10544 }, { 10545 Name: "tag2", 10546 }, { 10547 Name: "tag3", 10548 Description: "tag3 description", 10549 }}, 10550 newTags: []openapiTagObject{{ 10551 Name: "tag2", 10552 Description: "tag2 description", 10553 }}, 10554 expectedMergedTags: []openapiTagObject{{ 10555 Name: "tag1", 10556 Description: "tag1 description", 10557 }, { 10558 Name: "tag2", 10559 Description: "tag2 description", 10560 }, { 10561 Name: "tag3", 10562 Description: "tag3 description", 10563 }}, 10564 }, 10565 { 10566 testName: "Merge external docs", 10567 existingTags: []openapiTagObject{{ 10568 Name: "tag1", 10569 ExternalDocs: &openapiExternalDocumentationObject{}, 10570 }, { 10571 Name: "tag2", 10572 }, { 10573 Name: "tag3", 10574 ExternalDocs: &openapiExternalDocumentationObject{ 10575 Description: "tag3 description", 10576 }, 10577 }, { 10578 Name: "tag4", 10579 ExternalDocs: &openapiExternalDocumentationObject{ 10580 URL: "tag4 url", 10581 }, 10582 }}, 10583 newTags: []openapiTagObject{{ 10584 Name: "tag1", 10585 ExternalDocs: &openapiExternalDocumentationObject{ 10586 Description: "tag1 description", 10587 }, 10588 }, { 10589 Name: "tag2", 10590 ExternalDocs: &openapiExternalDocumentationObject{ 10591 Description: "tag2 description", 10592 URL: "tag2 url", 10593 }, 10594 }, { 10595 Name: "tag3", 10596 ExternalDocs: &openapiExternalDocumentationObject{ 10597 Description: "ignored tag3 description", 10598 URL: "tag3 url", 10599 }, 10600 }, { 10601 Name: "tag4", 10602 ExternalDocs: &openapiExternalDocumentationObject{ 10603 Description: "tag4 description", 10604 }, 10605 }}, 10606 expectedMergedTags: []openapiTagObject{{ 10607 Name: "tag1", 10608 ExternalDocs: &openapiExternalDocumentationObject{ 10609 Description: "tag1 description", 10610 }, 10611 }, { 10612 Name: "tag2", 10613 ExternalDocs: &openapiExternalDocumentationObject{ 10614 Description: "tag2 description", 10615 URL: "tag2 url", 10616 }, 10617 }, { 10618 Name: "tag3", 10619 ExternalDocs: &openapiExternalDocumentationObject{ 10620 Description: "tag3 description", 10621 URL: "tag3 url", 10622 }, 10623 }, { 10624 Name: "tag4", 10625 ExternalDocs: &openapiExternalDocumentationObject{ 10626 Description: "tag4 description", 10627 URL: "tag4 url", 10628 }, 10629 }}, 10630 }, 10631 { 10632 testName: "Merge extensions", 10633 existingTags: []openapiTagObject{{ 10634 Name: "tag1", 10635 extensions: []extension{{key: "x-key1", value: MustMarshal("key1 extension")}}, 10636 }, { 10637 Name: "tag2", 10638 extensions: []extension{ 10639 {key: "x-key1", value: MustMarshal("key1 extension")}, 10640 {key: "x-key2", value: MustMarshal("key2 extension")}, 10641 }, 10642 }, { 10643 Name: "tag3", 10644 extensions: []extension{ 10645 {key: "x-key1", value: MustMarshal("key1 extension")}, 10646 }, 10647 }, { 10648 Name: "tag4", 10649 extensions: nil, 10650 }}, 10651 newTags: []openapiTagObject{{ 10652 Name: "tag1", 10653 extensions: []extension{{key: "x-key2", value: MustMarshal("key2 extension")}}, 10654 }, { 10655 Name: "tag2", 10656 extensions: []extension{ 10657 {key: "x-key1", value: MustMarshal("key1 extension")}, 10658 {key: "x-key2", value: MustMarshal("ignored key2 extension")}, 10659 {key: "x-key3", value: MustMarshal("key3 extension")}, 10660 }, 10661 }, { 10662 Name: "tag3", 10663 extensions: nil, 10664 }, { 10665 Name: "tag4", 10666 extensions: []extension{ 10667 {key: "x-key1", value: MustMarshal("key1 extension")}, 10668 }, 10669 }}, 10670 expectedMergedTags: []openapiTagObject{{ 10671 Name: "tag1", 10672 extensions: []extension{ 10673 {key: "x-key1", value: MustMarshal("key1 extension")}, 10674 {key: "x-key2", value: MustMarshal("key2 extension")}, 10675 }, 10676 }, { 10677 Name: "tag2", 10678 extensions: []extension{ 10679 {key: "x-key1", value: MustMarshal("key1 extension")}, 10680 {key: "x-key2", value: MustMarshal("key2 extension")}, 10681 {key: "x-key3", value: MustMarshal("key3 extension")}, 10682 }, 10683 }, { 10684 Name: "tag3", 10685 extensions: []extension{ 10686 {key: "x-key1", value: MustMarshal("key1 extension")}, 10687 }, 10688 }, { 10689 Name: "tag4", 10690 extensions: []extension{ 10691 {key: "x-key1", value: MustMarshal("key1 extension")}, 10692 }, 10693 }}, 10694 }, 10695 } 10696 for _, tc := range testCases { 10697 tc := tc 10698 t.Run(tc.testName, func(t *testing.T) { 10699 mergedTags := mergeTags(tc.existingTags, tc.newTags) 10700 if !reflect.DeepEqual(tc.expectedMergedTags, mergedTags) { 10701 t.Fatalf("%s: Tags not correctly merged. Want %#v, got %#v", tc.testName, tc.expectedMergedTags, mergedTags) 10702 } 10703 }) 10704 } 10705 }