...

Source file src/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor/services_test.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor

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

View as plain text