github.com/jhump/protoreflect@v1.16.0/desc/builder/builder_test.go (about)

     1  package builder
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/golang/protobuf/proto"
    12  	"github.com/jhump/protoreflect/desc/protoparse"
    13  	"google.golang.org/protobuf/types/descriptorpb"
    14  	"google.golang.org/protobuf/types/known/anypb"
    15  	"google.golang.org/protobuf/types/known/emptypb"
    16  	"google.golang.org/protobuf/types/known/timestamppb"
    17  
    18  	"github.com/jhump/protoreflect/desc"
    19  	"github.com/jhump/protoreflect/dynamic"
    20  	_ "github.com/jhump/protoreflect/internal/testprotos"
    21  	"github.com/jhump/protoreflect/internal/testutil"
    22  )
    23  
    24  func TestSimpleDescriptorsFromScratch(t *testing.T) {
    25  	md, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil))
    26  	testutil.Ok(t, err)
    27  
    28  	file := NewFile("foo/bar.proto").SetPackageName("foo.bar")
    29  	en := NewEnum("Options").
    30  		AddValue(NewEnumValue("OPTION_1")).
    31  		AddValue(NewEnumValue("OPTION_2")).
    32  		AddValue(NewEnumValue("OPTION_3"))
    33  	file.AddEnum(en)
    34  
    35  	msg := NewMessage("FooRequest").
    36  		AddField(NewField("id", FieldTypeInt64())).
    37  		AddField(NewField("name", FieldTypeString())).
    38  		AddField(NewField("options", FieldTypeEnum(en)).
    39  			SetRepeated())
    40  	file.AddMessage(msg)
    41  
    42  	sb := NewService("FooService").
    43  		AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false))).
    44  		AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true)))
    45  	file.AddService(sb)
    46  
    47  	fd, err := file.Build()
    48  	testutil.Ok(t, err)
    49  
    50  	testutil.Eq(t, []*desc.FileDescriptor{md.GetFile()}, fd.GetDependencies())
    51  	testutil.Require(t, fd.FindEnum("foo.bar.Options") != nil)
    52  	testutil.Eq(t, 3, len(fd.FindEnum("foo.bar.Options").GetValues()))
    53  	testutil.Require(t, fd.FindMessage("foo.bar.FooRequest") != nil)
    54  	testutil.Eq(t, 3, len(fd.FindMessage("foo.bar.FooRequest").GetFields()))
    55  	testutil.Require(t, fd.FindService("foo.bar.FooService") != nil)
    56  	testutil.Eq(t, 2, len(fd.FindService("foo.bar.FooService").GetMethods()))
    57  
    58  	// building the others produces same results
    59  	ed, err := en.Build()
    60  	testutil.Ok(t, err)
    61  	testutil.Require(t, proto.Equal(ed.AsProto(), fd.FindEnum("foo.bar.Options").AsProto()))
    62  
    63  	md, err = msg.Build()
    64  	testutil.Ok(t, err)
    65  	testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("foo.bar.FooRequest").AsProto()))
    66  
    67  	sd, err := sb.Build()
    68  	testutil.Ok(t, err)
    69  	testutil.Require(t, proto.Equal(sd.AsProto(), fd.FindService("foo.bar.FooService").AsProto()))
    70  }
    71  
    72  func TestSimpleDescriptorsFromScratch_SyntheticFiles(t *testing.T) {
    73  	md, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil))
    74  	testutil.Ok(t, err)
    75  
    76  	en := NewEnum("Options")
    77  	en.AddValue(NewEnumValue("OPTION_1"))
    78  	en.AddValue(NewEnumValue("OPTION_2"))
    79  	en.AddValue(NewEnumValue("OPTION_3"))
    80  
    81  	msg := NewMessage("FooRequest")
    82  	msg.AddField(NewField("id", FieldTypeInt64()))
    83  	msg.AddField(NewField("name", FieldTypeString()))
    84  	msg.AddField(NewField("options", FieldTypeEnum(en)).
    85  		SetRepeated())
    86  
    87  	sb := NewService("FooService")
    88  	sb.AddMethod(NewMethod("DoSomething", RpcTypeMessage(msg, false), RpcTypeMessage(msg, false)))
    89  	sb.AddMethod(NewMethod("ReturnThings", RpcTypeImportedMessage(md, false), RpcTypeMessage(msg, true)))
    90  
    91  	sd, err := sb.Build()
    92  	testutil.Ok(t, err)
    93  	testutil.Eq(t, "FooService", sd.GetFullyQualifiedName())
    94  	testutil.Eq(t, 2, len(sd.GetMethods()))
    95  
    96  	// it imports google/protobuf/empty.proto and a synthetic file that has message
    97  	testutil.Eq(t, 2, len(sd.GetFile().GetDependencies()))
    98  	fd := sd.GetFile().GetDependencies()[0]
    99  	testutil.Eq(t, "google/protobuf/empty.proto", fd.GetName())
   100  	testutil.Eq(t, md.GetFile(), fd)
   101  	fd = sd.GetFile().GetDependencies()[1]
   102  	testutil.Require(t, strings.Contains(fd.GetName(), "generated"))
   103  	testutil.Require(t, fd.FindMessage("FooRequest") != nil)
   104  	testutil.Eq(t, 3, len(fd.FindMessage("FooRequest").GetFields()))
   105  
   106  	// this one imports only a synthetic file that has enum
   107  	testutil.Eq(t, 1, len(fd.GetDependencies()))
   108  	fd2 := fd.GetDependencies()[0]
   109  	testutil.Require(t, fd2.FindEnum("Options") != nil)
   110  	testutil.Eq(t, 3, len(fd2.FindEnum("Options").GetValues()))
   111  
   112  	// building the others produces same results
   113  	ed, err := en.Build()
   114  	testutil.Ok(t, err)
   115  	testutil.Require(t, proto.Equal(ed.AsProto(), fd2.FindEnum("Options").AsProto()))
   116  
   117  	md, err = msg.Build()
   118  	testutil.Ok(t, err)
   119  	testutil.Require(t, proto.Equal(md.AsProto(), fd.FindMessage("FooRequest").AsProto()))
   120  }
   121  
   122  func TestComplexDescriptorsFromScratch(t *testing.T) {
   123  	mdEmpty, err := desc.LoadMessageDescriptorForMessage((*emptypb.Empty)(nil))
   124  	testutil.Ok(t, err)
   125  	mdAny, err := desc.LoadMessageDescriptorForMessage((*anypb.Any)(nil))
   126  	testutil.Ok(t, err)
   127  	mdTimestamp, err := desc.LoadMessageDescriptorForMessage((*timestamppb.Timestamp)(nil))
   128  	testutil.Ok(t, err)
   129  	mbAny, err := FromMessage(mdAny)
   130  	testutil.Ok(t, err)
   131  
   132  	msgA := NewMessage("FooA").
   133  		AddField(NewField("id", FieldTypeUInt64())).
   134  		AddField(NewField("when", FieldTypeImportedMessage(mdTimestamp))).
   135  		AddField(NewField("extras", FieldTypeImportedMessage(mdAny)).
   136  			SetRepeated()).
   137  		AddField(NewField("builder", FieldTypeMessage(mbAny))).
   138  		SetExtensionRanges([]*descriptorpb.DescriptorProto_ExtensionRange{{Start: proto.Int32(100), End: proto.Int32(201)}})
   139  	msgA2 := NewMessage("Nnn").
   140  		AddField(NewField("uid1", FieldTypeFixed64())).
   141  		AddField(NewField("uid2", FieldTypeFixed64()))
   142  	NewFile("").
   143  		SetPackageName("foo.bar").
   144  		AddMessage(msgA).
   145  		AddMessage(msgA2)
   146  
   147  	msgB := NewMessage("FooB").
   148  		AddField(NewField("foo_a", FieldTypeMessage(msgA)).
   149  			SetRepeated()).
   150  		AddField(NewField("name", FieldTypeString()))
   151  	NewFile("").
   152  		SetPackageName("foo.bar").
   153  		AddMessage(msgB)
   154  
   155  	enC := NewEnum("Vals").
   156  		AddValue(NewEnumValue("DEFAULT")).
   157  		AddValue(NewEnumValue("VALUE_A")).
   158  		AddValue(NewEnumValue("VALUE_B")).
   159  		AddValue(NewEnumValue("VALUE_C"))
   160  	msgC := NewMessage("BarBaz").
   161  		AddOneOf(NewOneOf("bbb").
   162  			AddChoice(NewField("b1", FieldTypeMessage(msgA))).
   163  			AddChoice(NewField("b2", FieldTypeMessage(msgB)))).
   164  		AddField(NewField("v", FieldTypeEnum(enC)))
   165  	NewFile("some/path/file.proto").
   166  		SetPackageName("foo.baz").
   167  		AddEnum(enC).
   168  		AddMessage(msgC)
   169  
   170  	enD := NewEnum("Ppp").
   171  		AddValue(NewEnumValue("P0")).
   172  		AddValue(NewEnumValue("P1")).
   173  		AddValue(NewEnumValue("P2")).
   174  		AddValue(NewEnumValue("P3"))
   175  	exD := NewExtension("ppp", 123, FieldTypeEnum(enD), msgA)
   176  	NewFile("some/other/path/file.proto").
   177  		SetPackageName("foo.biz").
   178  		AddEnum(enD).
   179  		AddExtension(exD)
   180  
   181  	msgE := NewMessage("Ppp").
   182  		AddField(NewField("p", FieldTypeEnum(enD))).
   183  		AddField(NewField("n", FieldTypeMessage(msgA2)))
   184  	fd, err := NewFile("").
   185  		SetPackageName("foo.bar").
   186  		AddMessage(msgE).
   187  		AddService(NewService("PppSvc").
   188  			AddMethod(NewMethod("Method1", RpcTypeMessage(msgE, false), RpcTypeImportedMessage(mdEmpty, false))).
   189  			AddMethod(NewMethod("Method2", RpcTypeMessage(msgB, false), RpcTypeMessage(msgC, true)))).
   190  		Build()
   191  
   192  	testutil.Ok(t, err)
   193  
   194  	testutil.Eq(t, 5, len(fd.GetDependencies()))
   195  	// dependencies sorted; those with generated names come last
   196  	depEmpty := fd.GetDependencies()[0]
   197  	testutil.Eq(t, "google/protobuf/empty.proto", depEmpty.GetName())
   198  	testutil.Eq(t, mdEmpty.GetFile(), depEmpty)
   199  	depD := fd.GetDependencies()[1]
   200  	testutil.Eq(t, "some/other/path/file.proto", depD.GetName())
   201  	depC := fd.GetDependencies()[2]
   202  	testutil.Eq(t, "some/path/file.proto", depC.GetName())
   203  	depA := fd.GetDependencies()[3]
   204  	testutil.Require(t, strings.Contains(depA.GetName(), "generated"))
   205  	depB := fd.GetDependencies()[4]
   206  	testutil.Require(t, strings.Contains(depB.GetName(), "generated"))
   207  
   208  	// check contents of files
   209  	testutil.Require(t, depA.FindMessage("foo.bar.FooA") != nil)
   210  	testutil.Eq(t, 4, len(depA.FindMessage("foo.bar.FooA").GetFields()))
   211  	testutil.Require(t, depA.FindMessage("foo.bar.Nnn") != nil)
   212  	testutil.Eq(t, 2, len(depA.FindMessage("foo.bar.Nnn").GetFields()))
   213  	testutil.Eq(t, 2, len(depA.GetDependencies()))
   214  
   215  	testutil.Require(t, depB.FindMessage("foo.bar.FooB") != nil)
   216  	testutil.Eq(t, 2, len(depB.FindMessage("foo.bar.FooB").GetFields()))
   217  	testutil.Eq(t, 1, len(depB.GetDependencies()))
   218  
   219  	testutil.Require(t, depC.FindMessage("foo.baz.BarBaz") != nil)
   220  	testutil.Eq(t, 3, len(depC.FindMessage("foo.baz.BarBaz").GetFields()))
   221  	testutil.Require(t, depC.FindEnum("foo.baz.Vals") != nil)
   222  	testutil.Eq(t, 4, len(depC.FindEnum("foo.baz.Vals").GetValues()))
   223  	testutil.Eq(t, 2, len(depC.GetDependencies()))
   224  
   225  	testutil.Require(t, depD.FindEnum("foo.biz.Ppp") != nil)
   226  	testutil.Eq(t, 4, len(depD.FindEnum("foo.biz.Ppp").GetValues()))
   227  	testutil.Require(t, depD.FindExtensionByName("foo.biz.ppp") != nil)
   228  	testutil.Eq(t, 1, len(depD.GetDependencies()))
   229  
   230  	testutil.Require(t, fd.FindMessage("foo.bar.Ppp") != nil)
   231  	testutil.Eq(t, 2, len(fd.FindMessage("foo.bar.Ppp").GetFields()))
   232  	testutil.Require(t, fd.FindService("foo.bar.PppSvc") != nil)
   233  	testutil.Eq(t, 2, len(fd.FindService("foo.bar.PppSvc").GetMethods()))
   234  }
   235  
   236  func TestCreatingGroupField(t *testing.T) {
   237  	grpMb := NewMessage("GroupA").
   238  		AddField(NewField("name", FieldTypeString())).
   239  		AddField(NewField("id", FieldTypeInt64()))
   240  	grpFlb := NewGroupField(grpMb)
   241  
   242  	mb := NewMessage("TestMessage").
   243  		AddField(NewField("foo", FieldTypeBool())).
   244  		AddField(grpFlb)
   245  	md, err := mb.Build()
   246  	testutil.Ok(t, err)
   247  
   248  	testutil.Require(t, md.FindFieldByName("groupa") != nil)
   249  	testutil.Eq(t, descriptorpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("groupa").GetType())
   250  	nmd := md.GetNestedMessageTypes()[0]
   251  	testutil.Eq(t, "GroupA", nmd.GetName())
   252  	testutil.Eq(t, nmd, md.FindFieldByName("groupa").GetMessageType())
   253  
   254  	// try a rename that will fail
   255  	err = grpMb.TrySetName("fooBarBaz")
   256  	testutil.Require(t, err != nil)
   257  	testutil.Eq(t, "group name fooBarBaz must start with capital letter", err.Error())
   258  	// failed rename should not have modified any state
   259  	md2, err := mb.Build()
   260  	testutil.Ok(t, err)
   261  	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
   262  	// another attempt that will fail
   263  	err = grpFlb.TrySetName("foobarbaz")
   264  	testutil.Require(t, err != nil)
   265  	testutil.Eq(t, "cannot change name of group field TestMessage.groupa; change name of group instead", err.Error())
   266  	// again, no state should have been modified
   267  	md2, err = mb.Build()
   268  	testutil.Ok(t, err)
   269  	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
   270  
   271  	// and a rename that succeeds
   272  	err = grpMb.TrySetName("FooBarBaz")
   273  	testutil.Ok(t, err)
   274  	md, err = mb.Build()
   275  	testutil.Ok(t, err)
   276  
   277  	// field also renamed
   278  	testutil.Require(t, md.FindFieldByName("foobarbaz") != nil)
   279  	testutil.Eq(t, descriptorpb.FieldDescriptorProto_TYPE_GROUP, md.FindFieldByName("foobarbaz").GetType())
   280  	nmd = md.GetNestedMessageTypes()[0]
   281  	testutil.Eq(t, "FooBarBaz", nmd.GetName())
   282  	testutil.Eq(t, nmd, md.FindFieldByName("foobarbaz").GetMessageType())
   283  }
   284  
   285  func TestCreatingMapField(t *testing.T) {
   286  	mapFlb := NewMapField("countsByName", FieldTypeString(), FieldTypeUInt64())
   287  	testutil.Require(t, mapFlb.IsMap())
   288  
   289  	mb := NewMessage("TestMessage").
   290  		AddField(NewField("foo", FieldTypeBool())).
   291  		AddField(mapFlb)
   292  	md, err := mb.Build()
   293  	testutil.Ok(t, err)
   294  
   295  	testutil.Require(t, md.FindFieldByName("countsByName") != nil)
   296  	testutil.Require(t, md.FindFieldByName("countsByName").IsMap())
   297  	nmd := md.GetNestedMessageTypes()[0]
   298  	testutil.Eq(t, "CountsByNameEntry", nmd.GetName())
   299  	testutil.Eq(t, nmd, md.FindFieldByName("countsByName").GetMessageType())
   300  
   301  	// try a rename that will fail
   302  	err = mapFlb.GetType().localMsgType.TrySetName("fooBarBaz")
   303  	testutil.Require(t, err != nil)
   304  	testutil.Eq(t, "cannot change name of map entry TestMessage.CountsByNameEntry; change name of field instead", err.Error())
   305  	// failed rename should not have modified any state
   306  	md2, err := mb.Build()
   307  	testutil.Ok(t, err)
   308  	testutil.Require(t, proto.Equal(md.AsProto(), md2.AsProto()))
   309  
   310  	// and a rename that succeeds
   311  	err = mapFlb.TrySetName("fooBarBaz")
   312  	testutil.Ok(t, err)
   313  	md, err = mb.Build()
   314  	testutil.Ok(t, err)
   315  
   316  	// map entry also renamed
   317  	testutil.Require(t, md.FindFieldByName("fooBarBaz") != nil)
   318  	testutil.Require(t, md.FindFieldByName("fooBarBaz").IsMap())
   319  	nmd = md.GetNestedMessageTypes()[0]
   320  	testutil.Eq(t, "FooBarBazEntry", nmd.GetName())
   321  	testutil.Eq(t, nmd, md.FindFieldByName("fooBarBaz").GetMessageType())
   322  }
   323  
   324  func TestProto3Optional(t *testing.T) {
   325  	mb := NewMessage("Foo")
   326  	flb := NewField("bar", FieldTypeBool()).SetProto3Optional(true)
   327  	mb.AddField(flb)
   328  
   329  	_, err := flb.Build()
   330  	testutil.Nok(t, err) // file does not have proto3 syntax
   331  
   332  	fb := NewFile("foo.proto").SetProto3(true)
   333  	fb.AddMessage(mb)
   334  
   335  	fld, err := flb.Build()
   336  	testutil.Ok(t, err)
   337  
   338  	testutil.Require(t, fld.IsProto3Optional())
   339  	testutil.Require(t, fld.GetOneOf() != nil)
   340  	testutil.Require(t, fld.GetOneOf().IsSynthetic())
   341  	testutil.Eq(t, "_bar", fld.GetOneOf().GetName())
   342  }
   343  
   344  func TestBuildersFromDescriptors(t *testing.T) {
   345  	for _, s := range []string{
   346  		"desc_test1.proto", "desc_test2.proto",
   347  		"desc_test_defaults.proto", "desc_test_editions.proto", "desc_test_options.proto",
   348  		"desc_test_proto3.proto", "desc_test_wellknowntypes.proto",
   349  		"nopkg/desc_test_nopkg.proto", "nopkg/desc_test_nopkg_new.proto", "pkg/desc_test_pkg.proto",
   350  	} {
   351  		t.Run(s, func(t *testing.T) {
   352  			fd, err := desc.LoadFileDescriptor(s)
   353  			testutil.Ok(t, err)
   354  			roundTripFile(t, fd)
   355  		})
   356  	}
   357  }
   358  
   359  func TestBuildersFromDescriptors_PreserveComments(t *testing.T) {
   360  	fd, err := loadProtoset("../../internal/testprotos/desc_test1.protoset")
   361  	testutil.Ok(t, err)
   362  
   363  	fb, err := FromFile(fd)
   364  	testutil.Ok(t, err)
   365  
   366  	count := 0
   367  	var checkBuilderComments func(b Builder)
   368  	checkBuilderComments = func(b Builder) {
   369  		hasComment := true
   370  		switch b := b.(type) {
   371  		case *FileBuilder:
   372  			hasComment = false
   373  		case *FieldBuilder:
   374  			// comments for groups are on the message, not the field
   375  			hasComment = b.GetType().GetType() != descriptorpb.FieldDescriptorProto_TYPE_GROUP
   376  		case *MessageBuilder:
   377  			// comments for maps are on the field, not the entry message
   378  			if b.Options.GetMapEntry() {
   379  				// we just return to also skip checking child elements
   380  				// (map entry child elements are synthetic and have no comments)
   381  				return
   382  			}
   383  		}
   384  
   385  		if hasComment {
   386  			count++
   387  			testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", b.GetName()), b.GetComments().LeadingComment,
   388  				"wrong comment for builder %s", GetFullyQualifiedName(b))
   389  		}
   390  		for _, ch := range b.GetChildren() {
   391  			checkBuilderComments(ch)
   392  		}
   393  	}
   394  
   395  	checkBuilderComments(fb)
   396  	// sanity check that we didn't accidentally short-circuit above and fail to check comments
   397  	testutil.Require(t, count > 30, "too few elements checked")
   398  
   399  	// now check that they also come out in the resulting descriptor
   400  	fd, err = fb.Build()
   401  	testutil.Ok(t, err)
   402  
   403  	descCount := 0
   404  	var checkDescriptorComments func(d desc.Descriptor)
   405  	checkDescriptorComments = func(d desc.Descriptor) {
   406  		switch d := d.(type) {
   407  		case *desc.FileDescriptor:
   408  			for _, ch := range d.GetMessageTypes() {
   409  				checkDescriptorComments(ch)
   410  			}
   411  			for _, ch := range d.GetEnumTypes() {
   412  				checkDescriptorComments(ch)
   413  			}
   414  			for _, ch := range d.GetExtensions() {
   415  				checkDescriptorComments(ch)
   416  			}
   417  			for _, ch := range d.GetServices() {
   418  				checkDescriptorComments(ch)
   419  			}
   420  			// files don't have comments, so bail out before check below
   421  			return
   422  		case *desc.MessageDescriptor:
   423  			if d.IsMapEntry() {
   424  				// map entry messages have no comments (and neither do their child fields)
   425  				return
   426  			}
   427  			for _, ch := range d.GetFields() {
   428  				checkDescriptorComments(ch)
   429  			}
   430  			for _, ch := range d.GetNestedMessageTypes() {
   431  				checkDescriptorComments(ch)
   432  			}
   433  			for _, ch := range d.GetNestedEnumTypes() {
   434  				checkDescriptorComments(ch)
   435  			}
   436  			for _, ch := range d.GetNestedExtensions() {
   437  				checkDescriptorComments(ch)
   438  			}
   439  			for _, ch := range d.GetOneOfs() {
   440  				checkDescriptorComments(ch)
   441  			}
   442  		case *desc.FieldDescriptor:
   443  			if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP {
   444  				// groups comments are on the message, not hte field; so bail out before check below
   445  				return
   446  			}
   447  		case *desc.EnumDescriptor:
   448  			for _, ch := range d.GetValues() {
   449  				checkDescriptorComments(ch)
   450  			}
   451  		case *desc.ServiceDescriptor:
   452  			for _, ch := range d.GetMethods() {
   453  				checkDescriptorComments(ch)
   454  			}
   455  		}
   456  
   457  		descCount++
   458  		testutil.Eq(t, fmt.Sprintf(" Comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(),
   459  			"wrong comment for descriptor %s", d.GetFullyQualifiedName())
   460  	}
   461  
   462  	checkDescriptorComments(fd)
   463  	testutil.Eq(t, count, descCount)
   464  }
   465  
   466  func TestBuilder_PreserveAllCommentsAfterBuild(t *testing.T) {
   467  	files := map[string]string{"test.proto": `
   468  syntax = "proto3";
   469  
   470  // Leading detached comment for SimpleEnum
   471  
   472  // Leading comment for SimpleEnum
   473  enum SimpleEnum {
   474  // Trailing comment for SimpleEnum
   475  
   476    // Leading detached comment for VALUE0
   477  
   478    // Leading comment for VALUE0
   479    VALUE0 = 0; // Trailing comment for VALUE0
   480  }
   481  
   482  // Leading detached comment for SimpleMessage
   483  
   484  // Leading comment for SimpleMessage
   485  message SimpleMessage {
   486  // Trailing comment for SimpleMessage
   487  
   488    // Leading detached comment for field1
   489  
   490    // Leading comment for field1
   491    optional SimpleEnum field1 = 1; // Trailing comment for field1
   492  }
   493  `}
   494  
   495  	pa := &protoparse.Parser{
   496  		Accessor:              protoparse.FileContentsFromMap(files),
   497  		IncludeSourceCodeInfo: true,
   498  	}
   499  	fds, err := pa.ParseFiles("test.proto")
   500  	testutil.Ok(t, err)
   501  
   502  	fb, err := FromFile(fds[0])
   503  	testutil.Ok(t, err)
   504  
   505  	fd, err := fb.Build()
   506  	testutil.Ok(t, err)
   507  
   508  	var checkDescriptorComments func(d desc.Descriptor)
   509  	checkDescriptorComments = func(d desc.Descriptor) {
   510  		// fmt.Println(d.GetFullyQualifiedName(), d.GetSourceInfo().GetLeadingDetachedComments(), d.GetSourceInfo().GetLeadingComments(), d.GetSourceInfo().GetTrailingComments())
   511  		switch d := d.(type) {
   512  		case *desc.FileDescriptor:
   513  			for _, ch := range d.GetMessageTypes() {
   514  				checkDescriptorComments(ch)
   515  			}
   516  			for _, ch := range d.GetEnumTypes() {
   517  				checkDescriptorComments(ch)
   518  			}
   519  			// files don't have comments, so bail out before check below
   520  			return
   521  		case *desc.MessageDescriptor:
   522  			if d.IsMapEntry() {
   523  				// map entry messages have no comments (and neither do their child fields)
   524  				return
   525  			}
   526  			for _, ch := range d.GetFields() {
   527  				checkDescriptorComments(ch)
   528  			}
   529  		case *desc.FieldDescriptor:
   530  			if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP {
   531  				// groups comments are on the message, not hte field; so bail out before check below
   532  				return
   533  			}
   534  		case *desc.EnumDescriptor:
   535  			for _, ch := range d.GetValues() {
   536  				checkDescriptorComments(ch)
   537  			}
   538  		}
   539  		testutil.Eq(t, 1, len(d.GetSourceInfo().GetLeadingDetachedComments()),
   540  			"wrong number of leading detached comments for %s", d.GetFullyQualifiedName())
   541  		testutil.Eq(t, fmt.Sprintf(" Leading detached comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingDetachedComments()[0],
   542  			"wrong leading detached comment for descriptor %s", d.GetFullyQualifiedName())
   543  		testutil.Eq(t, fmt.Sprintf(" Leading comment for %s\n", d.GetName()), d.GetSourceInfo().GetLeadingComments(),
   544  			"wrong leading comment for descriptor %s", d.GetFullyQualifiedName())
   545  		testutil.Eq(t, fmt.Sprintf(" Trailing comment for %s\n", d.GetName()), d.GetSourceInfo().GetTrailingComments(),
   546  			"wrong trailing comment for descriptor %s", d.GetFullyQualifiedName())
   547  	}
   548  
   549  	checkDescriptorComments(fd)
   550  }
   551  
   552  func loadProtoset(path string) (*desc.FileDescriptor, error) {
   553  	var fds descriptorpb.FileDescriptorSet
   554  	f, err := os.Open(path)
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  	defer f.Close()
   559  	bb, err := io.ReadAll(f)
   560  	if err != nil {
   561  		return nil, err
   562  	}
   563  	if err = proto.Unmarshal(bb, &fds); err != nil {
   564  		return nil, err
   565  	}
   566  	return desc.CreateFileDescriptorFromSet(&fds)
   567  }
   568  
   569  func roundTripFile(t *testing.T, fd *desc.FileDescriptor) {
   570  	// First, recursively verify that every child element can be converted to a
   571  	// Builder and back without loss of fidelity.
   572  	for _, md := range fd.GetMessageTypes() {
   573  		roundTripMessage(t, md)
   574  	}
   575  	for _, ed := range fd.GetEnumTypes() {
   576  		roundTripEnum(t, ed)
   577  	}
   578  	for _, exd := range fd.GetExtensions() {
   579  		roundTripField(t, exd)
   580  	}
   581  	for _, sd := range fd.GetServices() {
   582  		roundTripService(t, sd)
   583  	}
   584  
   585  	// Finally, we check the whole file itself.
   586  	fb, err := FromFile(fd)
   587  	testutil.Ok(t, err)
   588  
   589  	roundTripped, err := fb.Build()
   590  	testutil.Ok(t, err)
   591  
   592  	// Round tripping from a file descriptor to a builder and back will
   593  	// experience some minor changes (that do not impact the semantics of
   594  	// any of the file's contents):
   595  	//  1. The builder sorts dependencies. However the original file
   596  	//     descriptor has dependencies in the order they appear in import
   597  	//     statements in the source file.
   598  	//  2. The builder imports the actual source of all elements and never
   599  	//     uses public imports. The original file, on the other hand, could
   600  	//     use public imports and "indirectly" import other files that way.
   601  	//  3. The builder never emits weak imports.
   602  	//  4. The builder behaves like protoc in that it emits nil as the file
   603  	//     package if none is set. However the new protobuf runtime, when
   604  	//     reconstructing the proto from a protoreflect.FileDescriptor, will
   605  	//     instead emit a pointer to empty string :(
   606  	//  5. The builder tries to preserve SourceCodeInfo, but will not preserve
   607  	//     position information. So that info does not survive round-tripping
   608  	//     (though comments do: there is a separate test for that). Also, the
   609  	//     round-tripped version will have source code info (even though it
   610  	//     may have no comments and zero position info), even if the original
   611  	//     descriptor had none.
   612  	// So we're going to modify the original descriptor in the same ways.
   613  	// That way, a simple proto.Equal() check will suffice to confirm that
   614  	// the file descriptor survived the round trip.
   615  
   616  	// The files we are testing have one occurrence of a public import. The
   617  	// file nopkg/desc_test_nopkg.proto declares nothing and public imports
   618  	// nopkg/desc_test_nopkg_new.proto. So any file that depends on the
   619  	// former will be updated to instead depend on the latter (since it is
   620  	// the actual file that declares used elements).
   621  	fdp := fd.AsFileDescriptorProto()
   622  	needsNopkgNew := false
   623  	hasNoPkgNew := false
   624  	for _, dep := range fdp.Dependency {
   625  		if dep == "nopkg/desc_test_nopkg.proto" {
   626  			needsNopkgNew = true
   627  		}
   628  		if dep == "nopkg/desc_test_nopkg_new.proto" {
   629  			hasNoPkgNew = false
   630  		}
   631  	}
   632  	if needsNopkgNew && !hasNoPkgNew {
   633  		fdp.Dependency = append(fdp.Dependency, "nopkg/desc_test_nopkg_new.proto")
   634  	}
   635  
   636  	// Strip any public and weak imports. (The step above should have "fixed"
   637  	// files to handle any actual public import encountered.)
   638  	fdp.PublicDependency = nil
   639  	fdp.WeakDependency = nil
   640  
   641  	// Fix the one we loaded so it uses nil as the package instead of an
   642  	// empty string, since that is what builders produce.
   643  	if fdp.GetPackage() == "" {
   644  		fdp.Package = nil
   645  	}
   646  
   647  	// Remove source code info: what the builder generates is not expected to
   648  	// match the original source.
   649  	fdp.SourceCodeInfo = nil
   650  	roundTripped.AsFileDescriptorProto().SourceCodeInfo = nil
   651  
   652  	// Finally, sort the imports. That way they match the built result (which
   653  	// is always sorted).
   654  	sort.Strings(fdp.Dependency)
   655  
   656  	// Now (after tweaking) the original should match the round-tripped descriptor:
   657  	testutil.Require(t, proto.Equal(fdp, roundTripped.AsProto()), "File %q failed round trip.\nExpecting: %s\nGot: %s\n",
   658  		fd.GetName(), proto.MarshalTextString(fdp), proto.MarshalTextString(roundTripped.AsProto()))
   659  }
   660  
   661  func roundTripMessage(t *testing.T, md *desc.MessageDescriptor) {
   662  	// first recursively validate all nested elements
   663  	for _, fld := range md.GetFields() {
   664  		roundTripField(t, fld)
   665  	}
   666  	for _, ood := range md.GetOneOfs() {
   667  		oob, err := FromOneOf(ood)
   668  		testutil.Ok(t, err)
   669  		roundTripped, err := oob.Build()
   670  		testutil.Ok(t, err)
   671  		checkDescriptors(t, ood, roundTripped)
   672  	}
   673  	for _, nmd := range md.GetNestedMessageTypes() {
   674  		roundTripMessage(t, nmd)
   675  	}
   676  	for _, ed := range md.GetNestedEnumTypes() {
   677  		roundTripEnum(t, ed)
   678  	}
   679  	for _, exd := range md.GetNestedExtensions() {
   680  		roundTripField(t, exd)
   681  	}
   682  
   683  	mb, err := FromMessage(md)
   684  	testutil.Ok(t, err)
   685  	roundTripped, err := mb.Build()
   686  	testutil.Ok(t, err)
   687  	checkDescriptors(t, md, roundTripped)
   688  }
   689  
   690  func roundTripEnum(t *testing.T, ed *desc.EnumDescriptor) {
   691  	// first recursively validate all nested elements
   692  	for _, evd := range ed.GetValues() {
   693  		evb, err := FromEnumValue(evd)
   694  		testutil.Ok(t, err)
   695  		roundTripped, err := evb.Build()
   696  		testutil.Ok(t, err)
   697  		checkDescriptors(t, evd, roundTripped)
   698  	}
   699  
   700  	eb, err := FromEnum(ed)
   701  	testutil.Ok(t, err)
   702  	roundTripped, err := eb.Build()
   703  	testutil.Ok(t, err)
   704  	checkDescriptors(t, ed, roundTripped)
   705  }
   706  
   707  func roundTripField(t *testing.T, fld *desc.FieldDescriptor) {
   708  	flb, err := FromField(fld)
   709  	testutil.Ok(t, err)
   710  	roundTripped, err := flb.Build()
   711  	testutil.Ok(t, err)
   712  	checkDescriptors(t, fld, roundTripped)
   713  }
   714  
   715  func roundTripService(t *testing.T, sd *desc.ServiceDescriptor) {
   716  	// first recursively validate all nested elements
   717  	for _, mtd := range sd.GetMethods() {
   718  		mtb, err := FromMethod(mtd)
   719  		testutil.Ok(t, err)
   720  		roundTripped, err := mtb.Build()
   721  		testutil.Ok(t, err)
   722  		checkDescriptors(t, mtd, roundTripped)
   723  	}
   724  
   725  	sb, err := FromService(sd)
   726  	testutil.Ok(t, err)
   727  	roundTripped, err := sb.Build()
   728  	testutil.Ok(t, err)
   729  	checkDescriptors(t, sd, roundTripped)
   730  }
   731  
   732  func checkDescriptors(t *testing.T, d1, d2 desc.Descriptor) {
   733  	testutil.Eq(t, d1.GetFullyQualifiedName(), d2.GetFullyQualifiedName())
   734  	testutil.Require(t, proto.Equal(d1.AsProto(), d2.AsProto()), "%s failed round trip.\nExpecting: %s\nGot: %s\n",
   735  		d1.GetFullyQualifiedName(), proto.MarshalTextString(d1.AsProto()), proto.MarshalTextString(d2.AsProto()))
   736  }
   737  
   738  func TestAddRemoveMoveBuilders(t *testing.T) {
   739  	// add field to one-of
   740  	fld1 := NewField("foo", FieldTypeInt32())
   741  	oo1 := NewOneOf("oofoo")
   742  	oo1.AddChoice(fld1)
   743  	checkChildren(t, oo1, fld1)
   744  	testutil.Eq(t, oo1.GetChoice("foo"), fld1)
   745  
   746  	// add one-of w/ field to a message
   747  	msg1 := NewMessage("foo")
   748  	msg1.AddOneOf(oo1)
   749  	checkChildren(t, msg1, oo1)
   750  	testutil.Eq(t, msg1.GetOneOf("oofoo"), oo1)
   751  	// field remains unchanged
   752  	testutil.Eq(t, fld1.GetParent(), oo1)
   753  	testutil.Eq(t, oo1.GetChoice("foo"), fld1)
   754  	// field also now registered with msg1
   755  	testutil.Eq(t, msg1.GetField("foo"), fld1)
   756  
   757  	// add empty one-of to message
   758  	oo2 := NewOneOf("oobar")
   759  	msg1.AddOneOf(oo2)
   760  	checkChildren(t, msg1, oo1, oo2)
   761  	testutil.Eq(t, msg1.GetOneOf("oobar"), oo2)
   762  	// now add field to that one-of
   763  	fld2 := NewField("bar", FieldTypeInt32())
   764  	oo2.AddChoice(fld2)
   765  	checkChildren(t, oo2, fld2)
   766  	testutil.Eq(t, oo2.GetChoice("bar"), fld2)
   767  	// field also now registered with msg1
   768  	testutil.Eq(t, msg1.GetField("bar"), fld2)
   769  
   770  	// add fails due to name collisions
   771  	fld1dup := NewField("foo", FieldTypeInt32())
   772  	err := oo1.TryAddChoice(fld1dup)
   773  	checkFailedAdd(t, err, oo1, fld1dup, "already contains field")
   774  	fld2 = NewField("bar", FieldTypeInt32())
   775  	err = msg1.TryAddField(fld2)
   776  	checkFailedAdd(t, err, msg1, fld2, "already contains element")
   777  	msg2 := NewMessage("oofoo")
   778  	// name collision can be different type
   779  	// (here, nested message conflicts with a one-of)
   780  	err = msg1.TryAddNestedMessage(msg2)
   781  	checkFailedAdd(t, err, msg1, msg2, "already contains element")
   782  
   783  	msg2 = NewMessage("baz")
   784  	msg1.AddNestedMessage(msg2)
   785  	checkChildren(t, msg1, oo1, oo2, msg2)
   786  	testutil.Eq(t, msg1.GetNestedMessage("baz"), msg2)
   787  
   788  	// can't add extension or map fields to one-of
   789  	ext1 := NewExtension("abc", 123, FieldTypeInt32(), msg1)
   790  	err = oo1.TryAddChoice(ext1)
   791  	checkFailedAdd(t, err, oo1, ext1, "is an extension, not a regular field")
   792  	err = msg1.TryAddField(ext1)
   793  	checkFailedAdd(t, err, msg1, ext1, "is an extension, not a regular field")
   794  	mapField := NewMapField("abc", FieldTypeInt32(), FieldTypeString())
   795  	err = oo1.TryAddChoice(mapField)
   796  	checkFailedAdd(t, err, oo1, mapField, "cannot add a map field")
   797  	// can add group field though
   798  	groupMsg := NewMessage("Group")
   799  	groupField := NewGroupField(groupMsg)
   800  	oo1.AddChoice(groupField)
   801  	checkChildren(t, oo1, fld1, groupField)
   802  	// adding map and group to msg succeeds
   803  	msg1.AddField(groupField)
   804  	msg1.AddField(mapField)
   805  	checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField)
   806  	// messages associated with map and group fields are not children of the
   807  	// message, but are in its scope and accessible via GetNestedMessage
   808  	testutil.Eq(t, msg1.GetNestedMessage("Group"), groupMsg)
   809  	testutil.Eq(t, msg1.GetNestedMessage("AbcEntry"), mapField.GetType().localMsgType)
   810  
   811  	// adding extension to message
   812  	ext2 := NewExtension("xyz", 234, FieldTypeInt32(), msg1)
   813  	msg1.AddNestedExtension(ext2)
   814  	checkChildren(t, msg1, oo1, oo2, msg2, groupField, mapField, ext2)
   815  	err = msg1.TryAddNestedExtension(ext1) // name collision
   816  	checkFailedAdd(t, err, msg1, ext1, "already contains element")
   817  	fld3 := NewField("ijk", FieldTypeString())
   818  	err = msg1.TryAddNestedExtension(fld3)
   819  	checkFailedAdd(t, err, msg1, fld3, "is not an extension")
   820  
   821  	// add enum values to enum
   822  	enumVal1 := NewEnumValue("A")
   823  	enum1 := NewEnum("bazel")
   824  	enum1.AddValue(enumVal1)
   825  	checkChildren(t, enum1, enumVal1)
   826  	testutil.Eq(t, enum1.GetValue("A"), enumVal1)
   827  	enumVal2 := NewEnumValue("B")
   828  	enum1.AddValue(enumVal2)
   829  	checkChildren(t, enum1, enumVal1, enumVal2)
   830  	testutil.Eq(t, enum1.GetValue("B"), enumVal2)
   831  	// fail w/ name collision
   832  	enumVal3 := NewEnumValue("B")
   833  	err = enum1.TryAddValue(enumVal3)
   834  	checkFailedAdd(t, err, enum1, enumVal3, "already contains value")
   835  
   836  	msg2.AddNestedEnum(enum1)
   837  	checkChildren(t, msg2, enum1)
   838  	testutil.Eq(t, msg2.GetNestedEnum("bazel"), enum1)
   839  	ext3 := NewExtension("bazel", 987, FieldTypeString(), msg2)
   840  	err = msg2.TryAddNestedExtension(ext3)
   841  	checkFailedAdd(t, err, msg2, ext3, "already contains element")
   842  
   843  	// services and methods
   844  	mtd1 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false))
   845  	svc1 := NewService("FooService")
   846  	svc1.AddMethod(mtd1)
   847  	checkChildren(t, svc1, mtd1)
   848  	testutil.Eq(t, svc1.GetMethod("foo"), mtd1)
   849  	mtd2 := NewMethod("foo", RpcTypeMessage(msg1, false), RpcTypeMessage(msg1, false))
   850  	err = svc1.TryAddMethod(mtd2)
   851  	checkFailedAdd(t, err, svc1, mtd2, "already contains method")
   852  
   853  	// finally, test adding things to  a file
   854  	fb := NewFile("")
   855  	fb.AddMessage(msg1)
   856  	checkChildren(t, fb, msg1)
   857  	testutil.Eq(t, fb.GetMessage("foo"), msg1)
   858  	fb.AddService(svc1)
   859  	checkChildren(t, fb, msg1, svc1)
   860  	testutil.Eq(t, fb.GetService("FooService"), svc1)
   861  	enum2 := NewEnum("fizzle")
   862  	fb.AddEnum(enum2)
   863  	checkChildren(t, fb, msg1, svc1, enum2)
   864  	testutil.Eq(t, fb.GetEnum("fizzle"), enum2)
   865  	ext3 = NewExtension("foosball", 123, FieldTypeInt32(), msg1)
   866  	fb.AddExtension(ext3)
   867  	checkChildren(t, fb, msg1, svc1, enum2, ext3)
   868  	testutil.Eq(t, fb.GetExtension("foosball"), ext3)
   869  
   870  	// errors and name collisions
   871  	err = fb.TryAddExtension(fld3)
   872  	checkFailedAdd(t, err, fb, fld3, "is not an extension")
   873  	msg3 := NewMessage("fizzle")
   874  	err = fb.TryAddMessage(msg3)
   875  	checkFailedAdd(t, err, fb, msg3, "already contains element")
   876  	enum3 := NewEnum("foosball")
   877  	err = fb.TryAddEnum(enum3)
   878  	checkFailedAdd(t, err, fb, enum3, "already contains element")
   879  
   880  	// TODO: test moving and removing, too
   881  }
   882  
   883  func checkChildren(t *testing.T, parent Builder, children ...Builder) {
   884  	testutil.Eq(t, len(children), len(parent.GetChildren()), "Wrong number of children for %s (%T)", GetFullyQualifiedName(parent), parent)
   885  	ch := map[Builder]struct{}{}
   886  	for _, child := range children {
   887  		testutil.Eq(t, child.GetParent(), parent, "Child %s (%T) does not report %s (%T) as its parent", child.GetName(), child, GetFullyQualifiedName(parent), parent)
   888  		ch[child] = struct{}{}
   889  	}
   890  	for _, child := range parent.GetChildren() {
   891  		_, ok := ch[child]
   892  		testutil.Require(t, ok, "Child %s (%T) does appear in list of children for %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent)
   893  	}
   894  }
   895  
   896  func checkFailedAdd(t *testing.T, err error, parent Builder, child Builder, errorMsg string) {
   897  	testutil.Require(t, err != nil, "Expecting error assigning %s (%T) to %s (%T)", child.GetName(), child, GetFullyQualifiedName(parent), parent)
   898  	testutil.Require(t, strings.Contains(err.Error(), errorMsg), "Expecting error assigning %s (%T) to %s (%T) to contain text %q: %q", child.GetName(), child, GetFullyQualifiedName(parent), parent, errorMsg, err.Error())
   899  	testutil.Eq(t, nil, child.GetParent(), "Child %s (%T) should not have a parent after failed add", child.GetName(), child)
   900  	for _, ch := range parent.GetChildren() {
   901  		testutil.Require(t, ch != child, "Child %s (%T) should not appear in list of children for %s (%T) but does", child.GetName(), child, GetFullyQualifiedName(parent), parent)
   902  	}
   903  }
   904  
   905  func TestRenamingBuilders(t *testing.T) {
   906  	// TODO
   907  }
   908  
   909  func TestRenumberingFields(t *testing.T) {
   910  	// TODO
   911  }
   912  
   913  var (
   914  	fileOptionsDesc, msgOptionsDesc, fieldOptionsDesc, oneofOptionsDesc, extRangeOptionsDesc,
   915  	enumOptionsDesc, enumValOptionsDesc, svcOptionsDesc, mtdOptionsDesc *desc.MessageDescriptor
   916  )
   917  
   918  func init() {
   919  	var err error
   920  	fileOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.FileOptions)(nil))
   921  	if err != nil {
   922  		panic(err)
   923  	}
   924  	msgOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.MessageOptions)(nil))
   925  	if err != nil {
   926  		panic(err)
   927  	}
   928  	fieldOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.FieldOptions)(nil))
   929  	if err != nil {
   930  		panic(err)
   931  	}
   932  	oneofOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.OneofOptions)(nil))
   933  	if err != nil {
   934  		panic(err)
   935  	}
   936  	extRangeOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.ExtensionRangeOptions)(nil))
   937  	if err != nil {
   938  		panic(err)
   939  	}
   940  	enumOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.EnumOptions)(nil))
   941  	if err != nil {
   942  		panic(err)
   943  	}
   944  	enumValOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.EnumValueOptions)(nil))
   945  	if err != nil {
   946  		panic(err)
   947  	}
   948  	svcOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.ServiceOptions)(nil))
   949  	if err != nil {
   950  		panic(err)
   951  	}
   952  	mtdOptionsDesc, err = desc.LoadMessageDescriptorForMessage((*descriptorpb.MethodOptions)(nil))
   953  	if err != nil {
   954  		panic(err)
   955  	}
   956  }
   957  
   958  func TestCustomOptionsDiscoveredInSameFile(t *testing.T) {
   959  	// Add option for every type to file
   960  	file := NewFile("foo.proto")
   961  
   962  	fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc)
   963  	file.AddExtension(fileOpt)
   964  
   965  	msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc)
   966  	file.AddExtension(msgOpt)
   967  
   968  	fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc)
   969  	file.AddExtension(fieldOpt)
   970  
   971  	oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc)
   972  	file.AddExtension(oneofOpt)
   973  
   974  	extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc)
   975  	file.AddExtension(extRangeOpt)
   976  
   977  	enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc)
   978  	file.AddExtension(enumOpt)
   979  
   980  	enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc)
   981  	file.AddExtension(enumValOpt)
   982  
   983  	svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc)
   984  	file.AddExtension(svcOpt)
   985  
   986  	mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc)
   987  	file.AddExtension(mtdOpt)
   988  
   989  	// Now we can test referring to these and making sure they show up correctly
   990  	// in built descriptors
   991  
   992  	t.Run("file options", func(t *testing.T) {
   993  		fb := clone(t, file)
   994  		fb.Options = &descriptorpb.FileOptions{}
   995  		ext, err := fileOpt.Build()
   996  		testutil.Ok(t, err)
   997  		err = dynamic.SetExtension(fb.Options, ext, "fubar")
   998  		testutil.Ok(t, err)
   999  		checkBuildWithLocalExtensions(t, fb)
  1000  	})
  1001  
  1002  	t.Run("message options", func(t *testing.T) {
  1003  		mb := NewMessage("Foo")
  1004  		mb.Options = &descriptorpb.MessageOptions{}
  1005  		ext, err := msgOpt.Build()
  1006  		testutil.Ok(t, err)
  1007  		err = dynamic.SetExtension(mb.Options, ext, "fubar")
  1008  		testutil.Ok(t, err)
  1009  
  1010  		fb := clone(t, file)
  1011  		fb.AddMessage(mb)
  1012  		checkBuildWithLocalExtensions(t, mb)
  1013  	})
  1014  
  1015  	t.Run("field options", func(t *testing.T) {
  1016  		flb := NewField("foo", FieldTypeString())
  1017  		flb.Options = &descriptorpb.FieldOptions{}
  1018  		// fields must be connected to a message
  1019  		mb := NewMessage("Foo").AddField(flb)
  1020  		ext, err := fieldOpt.Build()
  1021  		testutil.Ok(t, err)
  1022  		err = dynamic.SetExtension(flb.Options, ext, "fubar")
  1023  		testutil.Ok(t, err)
  1024  
  1025  		fb := clone(t, file)
  1026  		fb.AddMessage(mb)
  1027  		checkBuildWithLocalExtensions(t, flb)
  1028  	})
  1029  
  1030  	t.Run("oneof options", func(t *testing.T) {
  1031  		oob := NewOneOf("oo")
  1032  		oob.AddChoice(NewField("foo", FieldTypeString()))
  1033  		oob.Options = &descriptorpb.OneofOptions{}
  1034  		// oneofs must be connected to a message
  1035  		mb := NewMessage("Foo").AddOneOf(oob)
  1036  		ext, err := oneofOpt.Build()
  1037  		testutil.Ok(t, err)
  1038  		err = dynamic.SetExtension(oob.Options, ext, "fubar")
  1039  		testutil.Ok(t, err)
  1040  
  1041  		fb := clone(t, file)
  1042  		fb.AddMessage(mb)
  1043  		checkBuildWithLocalExtensions(t, oob)
  1044  	})
  1045  
  1046  	t.Run("extension range options", func(t *testing.T) {
  1047  		var erOpts descriptorpb.ExtensionRangeOptions
  1048  		ext, err := extRangeOpt.Build()
  1049  		testutil.Ok(t, err)
  1050  		err = dynamic.SetExtension(&erOpts, ext, "fubar")
  1051  		testutil.Ok(t, err)
  1052  		mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
  1053  
  1054  		fb := clone(t, file)
  1055  		fb.AddMessage(mb)
  1056  		checkBuildWithLocalExtensions(t, mb)
  1057  	})
  1058  
  1059  	t.Run("enum options", func(t *testing.T) {
  1060  		eb := NewEnum("Foo")
  1061  		eb.AddValue(NewEnumValue("FOO"))
  1062  		eb.Options = &descriptorpb.EnumOptions{}
  1063  		ext, err := enumOpt.Build()
  1064  		testutil.Ok(t, err)
  1065  		err = dynamic.SetExtension(eb.Options, ext, "fubar")
  1066  		testutil.Ok(t, err)
  1067  
  1068  		fb := clone(t, file)
  1069  		fb.AddEnum(eb)
  1070  		checkBuildWithLocalExtensions(t, eb)
  1071  	})
  1072  
  1073  	t.Run("enum val options", func(t *testing.T) {
  1074  		evb := NewEnumValue("FOO")
  1075  		// enum values must be connected to an enum
  1076  		eb := NewEnum("Foo").AddValue(evb)
  1077  		evb.Options = &descriptorpb.EnumValueOptions{}
  1078  		ext, err := enumValOpt.Build()
  1079  		testutil.Ok(t, err)
  1080  		err = dynamic.SetExtension(evb.Options, ext, "fubar")
  1081  		testutil.Ok(t, err)
  1082  
  1083  		fb := clone(t, file)
  1084  		fb.AddEnum(eb)
  1085  		checkBuildWithLocalExtensions(t, evb)
  1086  	})
  1087  
  1088  	t.Run("service options", func(t *testing.T) {
  1089  		sb := NewService("Foo")
  1090  		sb.Options = &descriptorpb.ServiceOptions{}
  1091  		ext, err := svcOpt.Build()
  1092  		testutil.Ok(t, err)
  1093  		err = dynamic.SetExtension(sb.Options, ext, "fubar")
  1094  		testutil.Ok(t, err)
  1095  
  1096  		fb := clone(t, file)
  1097  		fb.AddService(sb)
  1098  		checkBuildWithLocalExtensions(t, sb)
  1099  	})
  1100  
  1101  	t.Run("method options", func(t *testing.T) {
  1102  		req := NewMessage("Request")
  1103  		resp := NewMessage("Response")
  1104  		mtb := NewMethod("Foo",
  1105  			RpcTypeMessage(req, false),
  1106  			RpcTypeMessage(resp, false))
  1107  		// methods must be connected to a service
  1108  		sb := NewService("Bar").AddMethod(mtb)
  1109  		mtb.Options = &descriptorpb.MethodOptions{}
  1110  		ext, err := mtdOpt.Build()
  1111  		testutil.Ok(t, err)
  1112  		err = dynamic.SetExtension(mtb.Options, ext, "fubar")
  1113  		testutil.Ok(t, err)
  1114  
  1115  		fb := clone(t, file)
  1116  		fb.AddService(sb).AddMessage(req).AddMessage(resp)
  1117  		checkBuildWithLocalExtensions(t, mtb)
  1118  	})
  1119  }
  1120  
  1121  func checkBuildWithLocalExtensions(t *testing.T, builder Builder) {
  1122  	// requiring options and succeeding (since they are defined locally)
  1123  	var opts BuilderOptions
  1124  	opts.RequireInterpretedOptions = true
  1125  	d, err := opts.Build(builder)
  1126  	testutil.Ok(t, err)
  1127  	// since they are defined locally, no extra imports
  1128  	testutil.Eq(t, []string{"google/protobuf/descriptor.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency())
  1129  }
  1130  
  1131  func TestCustomOptionsDiscoveredInDependencies(t *testing.T) {
  1132  	// Add option for every type to file
  1133  	file := NewFile("options.proto")
  1134  
  1135  	fileOpt := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc)
  1136  	file.AddExtension(fileOpt)
  1137  
  1138  	msgOpt := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc)
  1139  	file.AddExtension(msgOpt)
  1140  
  1141  	fieldOpt := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc)
  1142  	file.AddExtension(fieldOpt)
  1143  
  1144  	oneofOpt := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc)
  1145  	file.AddExtension(oneofOpt)
  1146  
  1147  	extRangeOpt := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc)
  1148  	file.AddExtension(extRangeOpt)
  1149  
  1150  	enumOpt := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc)
  1151  	file.AddExtension(enumOpt)
  1152  
  1153  	enumValOpt := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc)
  1154  	file.AddExtension(enumValOpt)
  1155  
  1156  	svcOpt := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc)
  1157  	file.AddExtension(svcOpt)
  1158  
  1159  	mtdOpt := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc)
  1160  	file.AddExtension(mtdOpt)
  1161  
  1162  	fileDesc, err := file.Build()
  1163  	testutil.Ok(t, err)
  1164  
  1165  	// Another file that imports the options. Since it's not a public import, presence
  1166  	// of this file should not prevent builder from correctly adding options.proto dependency
  1167  	// in the "auto" case below.
  1168  	otherFileDesc, err := NewFile("other.proto").AddImportedDependency(fileDesc).Build()
  1169  	testutil.Ok(t, err)
  1170  
  1171  	regWithOpts := &dynamic.ExtensionRegistry{}
  1172  	regWithOpts.AddExtensionsFromFileRecursively(fileDesc)
  1173  
  1174  	// Now we can test referring to these and making sure they show up correctly
  1175  	// in built descriptors
  1176  	for name, useBuilder := range map[string]*bool{"descriptor": proto.Bool(false), "builder": proto.Bool(true), "auto": nil} {
  1177  		newFile := func() *FileBuilder {
  1178  			fb := NewFile("foo.proto").AddImportedDependency(otherFileDesc)
  1179  			if useBuilder != nil {
  1180  				if *useBuilder {
  1181  					fb.AddDependency(file)
  1182  				} else {
  1183  					fb.AddImportedDependency(fileDesc)
  1184  				}
  1185  			}
  1186  			return fb
  1187  		}
  1188  		var extReg *dynamic.ExtensionRegistry
  1189  		if useBuilder == nil {
  1190  			// if providing neither builder nor descriptor, we need to provide
  1191  			// a registry for resolving custom options
  1192  			extReg = regWithOpts
  1193  		}
  1194  		t.Run(name, func(t *testing.T) {
  1195  			t.Run("file options", func(t *testing.T) {
  1196  				fb := newFile()
  1197  				fb.Options = &descriptorpb.FileOptions{}
  1198  				ext, err := fileOpt.Build()
  1199  				testutil.Ok(t, err)
  1200  				err = dynamic.SetExtension(fb.Options, ext, "fubar")
  1201  				testutil.Ok(t, err)
  1202  				checkBuildWithImportedExtensions(t, fb, extReg)
  1203  			})
  1204  
  1205  			t.Run("message options", func(t *testing.T) {
  1206  				mb := NewMessage("Foo")
  1207  				mb.Options = &descriptorpb.MessageOptions{}
  1208  				ext, err := msgOpt.Build()
  1209  				testutil.Ok(t, err)
  1210  				err = dynamic.SetExtension(mb.Options, ext, "fubar")
  1211  				testutil.Ok(t, err)
  1212  
  1213  				fb := newFile()
  1214  				fb.AddMessage(mb)
  1215  				checkBuildWithImportedExtensions(t, mb, extReg)
  1216  			})
  1217  
  1218  			t.Run("field options", func(t *testing.T) {
  1219  				flb := NewField("foo", FieldTypeString())
  1220  				flb.Options = &descriptorpb.FieldOptions{}
  1221  				// fields must be connected to a message
  1222  				mb := NewMessage("Foo").AddField(flb)
  1223  				ext, err := fieldOpt.Build()
  1224  				testutil.Ok(t, err)
  1225  				err = dynamic.SetExtension(flb.Options, ext, "fubar")
  1226  				testutil.Ok(t, err)
  1227  
  1228  				fb := newFile()
  1229  				fb.AddMessage(mb)
  1230  				checkBuildWithImportedExtensions(t, flb, extReg)
  1231  			})
  1232  
  1233  			t.Run("oneof options", func(t *testing.T) {
  1234  				oob := NewOneOf("oo")
  1235  				oob.AddChoice(NewField("foo", FieldTypeString()))
  1236  				oob.Options = &descriptorpb.OneofOptions{}
  1237  				// oneofs must be connected to a message
  1238  				mb := NewMessage("Foo").AddOneOf(oob)
  1239  				ext, err := oneofOpt.Build()
  1240  				testutil.Ok(t, err)
  1241  				err = dynamic.SetExtension(oob.Options, ext, "fubar")
  1242  				testutil.Ok(t, err)
  1243  
  1244  				fb := newFile()
  1245  				fb.AddMessage(mb)
  1246  				checkBuildWithImportedExtensions(t, oob, extReg)
  1247  			})
  1248  
  1249  			t.Run("extension range options", func(t *testing.T) {
  1250  				var erOpts descriptorpb.ExtensionRangeOptions
  1251  				ext, err := extRangeOpt.Build()
  1252  				testutil.Ok(t, err)
  1253  				err = dynamic.SetExtension(&erOpts, ext, "fubar")
  1254  				testutil.Ok(t, err)
  1255  				mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
  1256  
  1257  				fb := newFile()
  1258  				fb.AddMessage(mb)
  1259  				checkBuildWithImportedExtensions(t, mb, extReg)
  1260  			})
  1261  
  1262  			t.Run("enum options", func(t *testing.T) {
  1263  				eb := NewEnum("Foo")
  1264  				eb.AddValue(NewEnumValue("FOO"))
  1265  				eb.Options = &descriptorpb.EnumOptions{}
  1266  				ext, err := enumOpt.Build()
  1267  				testutil.Ok(t, err)
  1268  				err = dynamic.SetExtension(eb.Options, ext, "fubar")
  1269  				testutil.Ok(t, err)
  1270  
  1271  				fb := newFile()
  1272  				fb.AddEnum(eb)
  1273  				checkBuildWithImportedExtensions(t, eb, extReg)
  1274  			})
  1275  
  1276  			t.Run("enum val options", func(t *testing.T) {
  1277  				evb := NewEnumValue("FOO")
  1278  				// enum values must be connected to an enum
  1279  				eb := NewEnum("Foo").AddValue(evb)
  1280  				evb.Options = &descriptorpb.EnumValueOptions{}
  1281  				ext, err := enumValOpt.Build()
  1282  				testutil.Ok(t, err)
  1283  				err = dynamic.SetExtension(evb.Options, ext, "fubar")
  1284  				testutil.Ok(t, err)
  1285  
  1286  				fb := newFile()
  1287  				fb.AddEnum(eb)
  1288  				checkBuildWithImportedExtensions(t, evb, extReg)
  1289  			})
  1290  
  1291  			t.Run("service options", func(t *testing.T) {
  1292  				sb := NewService("Foo")
  1293  				sb.Options = &descriptorpb.ServiceOptions{}
  1294  				ext, err := svcOpt.Build()
  1295  				testutil.Ok(t, err)
  1296  				err = dynamic.SetExtension(sb.Options, ext, "fubar")
  1297  				testutil.Ok(t, err)
  1298  
  1299  				fb := newFile()
  1300  				fb.AddService(sb)
  1301  				checkBuildWithImportedExtensions(t, sb, extReg)
  1302  			})
  1303  
  1304  			t.Run("method options", func(t *testing.T) {
  1305  				req := NewMessage("Request")
  1306  				resp := NewMessage("Response")
  1307  				mtb := NewMethod("Foo",
  1308  					RpcTypeMessage(req, false),
  1309  					RpcTypeMessage(resp, false))
  1310  				// methods must be connected to a service
  1311  				sb := NewService("Bar").AddMethod(mtb)
  1312  				mtb.Options = &descriptorpb.MethodOptions{}
  1313  				ext, err := mtdOpt.Build()
  1314  				testutil.Ok(t, err)
  1315  				err = dynamic.SetExtension(mtb.Options, ext, "fubar")
  1316  				testutil.Ok(t, err)
  1317  
  1318  				fb := newFile()
  1319  				fb.AddService(sb).AddMessage(req).AddMessage(resp)
  1320  				checkBuildWithImportedExtensions(t, mtb, extReg)
  1321  			})
  1322  		})
  1323  	}
  1324  }
  1325  
  1326  func checkBuildWithImportedExtensions(t *testing.T, builder Builder, extReg *dynamic.ExtensionRegistry) {
  1327  	// requiring options and succeeding (since they are defined in explicit import)
  1328  	opts := BuilderOptions{
  1329  		RequireInterpretedOptions: true,
  1330  		Extensions:                extReg,
  1331  	}
  1332  	d, err := opts.Build(builder)
  1333  	testutil.Ok(t, err)
  1334  	// the only import is the explicitly added one and one added for the custom options
  1335  	testutil.Eq(t, []string{"options.proto", "other.proto"}, d.GetFile().AsFileDescriptorProto().GetDependency())
  1336  }
  1337  
  1338  func TestUseOfExtensionRegistry(t *testing.T) {
  1339  	// Add option for every type to extension registry
  1340  	var exts dynamic.ExtensionRegistry
  1341  
  1342  	fileOpt, err := NewExtensionImported("file_foo", 54321, FieldTypeString(), fileOptionsDesc).Build()
  1343  	testutil.Ok(t, err)
  1344  	err = exts.AddExtension(fileOpt)
  1345  	testutil.Ok(t, err)
  1346  
  1347  	msgOpt, err := NewExtensionImported("msg_foo", 54321, FieldTypeString(), msgOptionsDesc).Build()
  1348  	testutil.Ok(t, err)
  1349  	err = exts.AddExtension(msgOpt)
  1350  	testutil.Ok(t, err)
  1351  
  1352  	fieldOpt, err := NewExtensionImported("field_foo", 54321, FieldTypeString(), fieldOptionsDesc).Build()
  1353  	testutil.Ok(t, err)
  1354  	err = exts.AddExtension(fieldOpt)
  1355  	testutil.Ok(t, err)
  1356  
  1357  	oneofOpt, err := NewExtensionImported("oneof_foo", 54321, FieldTypeString(), oneofOptionsDesc).Build()
  1358  	testutil.Ok(t, err)
  1359  	err = exts.AddExtension(oneofOpt)
  1360  	testutil.Ok(t, err)
  1361  
  1362  	extRangeOpt, err := NewExtensionImported("ext_range_foo", 54321, FieldTypeString(), extRangeOptionsDesc).Build()
  1363  	testutil.Ok(t, err)
  1364  	err = exts.AddExtension(extRangeOpt)
  1365  	testutil.Ok(t, err)
  1366  
  1367  	enumOpt, err := NewExtensionImported("enum_foo", 54321, FieldTypeString(), enumOptionsDesc).Build()
  1368  	testutil.Ok(t, err)
  1369  	err = exts.AddExtension(enumOpt)
  1370  	testutil.Ok(t, err)
  1371  
  1372  	enumValOpt, err := NewExtensionImported("enum_val_foo", 54321, FieldTypeString(), enumValOptionsDesc).Build()
  1373  	testutil.Ok(t, err)
  1374  	err = exts.AddExtension(enumValOpt)
  1375  	testutil.Ok(t, err)
  1376  
  1377  	svcOpt, err := NewExtensionImported("svc_foo", 54321, FieldTypeString(), svcOptionsDesc).Build()
  1378  	testutil.Ok(t, err)
  1379  	err = exts.AddExtension(svcOpt)
  1380  	testutil.Ok(t, err)
  1381  
  1382  	mtdOpt, err := NewExtensionImported("mtd_foo", 54321, FieldTypeString(), mtdOptionsDesc).Build()
  1383  	testutil.Ok(t, err)
  1384  	err = exts.AddExtension(mtdOpt)
  1385  	testutil.Ok(t, err)
  1386  
  1387  	// Now we can test referring to these and making sure they show up correctly
  1388  	// in built descriptors
  1389  
  1390  	t.Run("file options", func(t *testing.T) {
  1391  		fb := NewFile("foo.proto")
  1392  		fb.Options = &descriptorpb.FileOptions{}
  1393  		err = dynamic.SetExtension(fb.Options, fileOpt, "fubar")
  1394  		testutil.Ok(t, err)
  1395  		checkBuildWithExtensions(t, &exts, fileOpt.GetFile(), fb)
  1396  	})
  1397  
  1398  	t.Run("message options", func(t *testing.T) {
  1399  		mb := NewMessage("Foo")
  1400  		mb.Options = &descriptorpb.MessageOptions{}
  1401  		err = dynamic.SetExtension(mb.Options, msgOpt, "fubar")
  1402  		testutil.Ok(t, err)
  1403  		checkBuildWithExtensions(t, &exts, msgOpt.GetFile(), mb)
  1404  	})
  1405  
  1406  	t.Run("field options", func(t *testing.T) {
  1407  		flb := NewField("foo", FieldTypeString())
  1408  		flb.Options = &descriptorpb.FieldOptions{}
  1409  		// fields must be connected to a message
  1410  		NewMessage("Foo").AddField(flb)
  1411  		err = dynamic.SetExtension(flb.Options, fieldOpt, "fubar")
  1412  		testutil.Ok(t, err)
  1413  		checkBuildWithExtensions(t, &exts, fieldOpt.GetFile(), flb)
  1414  	})
  1415  
  1416  	t.Run("oneof options", func(t *testing.T) {
  1417  		oob := NewOneOf("oo")
  1418  		oob.AddChoice(NewField("foo", FieldTypeString()))
  1419  		oob.Options = &descriptorpb.OneofOptions{}
  1420  		// oneofs must be connected to a message
  1421  		NewMessage("Foo").AddOneOf(oob)
  1422  		err = dynamic.SetExtension(oob.Options, oneofOpt, "fubar")
  1423  		testutil.Ok(t, err)
  1424  		checkBuildWithExtensions(t, &exts, oneofOpt.GetFile(), oob)
  1425  	})
  1426  
  1427  	t.Run("extension range options", func(t *testing.T) {
  1428  		var erOpts descriptorpb.ExtensionRangeOptions
  1429  		err = dynamic.SetExtension(&erOpts, extRangeOpt, "fubar")
  1430  		testutil.Ok(t, err)
  1431  		mb := NewMessage("foo").AddExtensionRangeWithOptions(100, 200, &erOpts)
  1432  		checkBuildWithExtensions(t, &exts, extRangeOpt.GetFile(), mb)
  1433  	})
  1434  
  1435  	t.Run("enum options", func(t *testing.T) {
  1436  		eb := NewEnum("Foo")
  1437  		eb.AddValue(NewEnumValue("FOO"))
  1438  		eb.Options = &descriptorpb.EnumOptions{}
  1439  		err = dynamic.SetExtension(eb.Options, enumOpt, "fubar")
  1440  		testutil.Ok(t, err)
  1441  		checkBuildWithExtensions(t, &exts, enumOpt.GetFile(), eb)
  1442  	})
  1443  
  1444  	t.Run("enum val options", func(t *testing.T) {
  1445  		evb := NewEnumValue("FOO")
  1446  		// enum values must be connected to an enum
  1447  		NewEnum("Foo").AddValue(evb)
  1448  		evb.Options = &descriptorpb.EnumValueOptions{}
  1449  		err = dynamic.SetExtension(evb.Options, enumValOpt, "fubar")
  1450  		testutil.Ok(t, err)
  1451  		checkBuildWithExtensions(t, &exts, enumValOpt.GetFile(), evb)
  1452  	})
  1453  
  1454  	t.Run("service options", func(t *testing.T) {
  1455  		sb := NewService("Foo")
  1456  		sb.Options = &descriptorpb.ServiceOptions{}
  1457  		err = dynamic.SetExtension(sb.Options, svcOpt, "fubar")
  1458  		testutil.Ok(t, err)
  1459  		checkBuildWithExtensions(t, &exts, svcOpt.GetFile(), sb)
  1460  	})
  1461  
  1462  	t.Run("method options", func(t *testing.T) {
  1463  		mtb := NewMethod("Foo",
  1464  			RpcTypeMessage(NewMessage("Request"), false),
  1465  			RpcTypeMessage(NewMessage("Response"), false))
  1466  		// methods must be connected to a service
  1467  		NewService("Bar").AddMethod(mtb)
  1468  		mtb.Options = &descriptorpb.MethodOptions{}
  1469  		err = dynamic.SetExtension(mtb.Options, mtdOpt, "fubar")
  1470  		testutil.Ok(t, err)
  1471  		checkBuildWithExtensions(t, &exts, mtdOpt.GetFile(), mtb)
  1472  	})
  1473  }
  1474  
  1475  func checkBuildWithExtensions(t *testing.T, exts *dynamic.ExtensionRegistry, expected *desc.FileDescriptor, builder Builder) {
  1476  	// without interpreting custom option
  1477  	d, err := builder.BuildDescriptor()
  1478  	testutil.Ok(t, err)
  1479  	for _, dep := range d.GetFile().GetDependencies() {
  1480  		testutil.Neq(t, expected, dep)
  1481  	}
  1482  	numDeps := len(d.GetFile().GetDependencies())
  1483  
  1484  	// requiring options (and failing)
  1485  	var opts BuilderOptions
  1486  	opts.RequireInterpretedOptions = true
  1487  	_, err = opts.Build(builder)
  1488  	testutil.Require(t, err != nil)
  1489  
  1490  	// able to interpret options via extension registry
  1491  	opts.Extensions = exts
  1492  	d, err = opts.Build(builder)
  1493  	testutil.Ok(t, err)
  1494  	testutil.Eq(t, numDeps+1, len(d.GetFile().GetDependencies()))
  1495  	found := false
  1496  	for _, dep := range d.GetFile().GetDependencies() {
  1497  		if expected == dep {
  1498  			found = true
  1499  			break
  1500  		}
  1501  	}
  1502  	testutil.Require(t, found)
  1503  }
  1504  
  1505  func TestRemoveField(t *testing.T) {
  1506  	msg := NewMessage("FancyMessage").
  1507  		AddField(NewField("one", FieldTypeInt64())).
  1508  		AddField(NewField("two", FieldTypeString())).
  1509  		AddField(NewField("three", FieldTypeString()))
  1510  
  1511  	ok := msg.TryRemoveField("two")
  1512  	children := msg.GetChildren()
  1513  
  1514  	testutil.Require(t, ok)
  1515  	testutil.Eq(t, 2, len(children))
  1516  	testutil.Eq(t, "one", children[0].GetName())
  1517  	testutil.Eq(t, "three", children[1].GetName())
  1518  }
  1519  
  1520  func TestInterleavedFieldNumbers(t *testing.T) {
  1521  	msg := NewMessage("MessageWithInterleavedFieldNumbers").
  1522  		AddField(NewField("one", FieldTypeInt64()).SetNumber(1)).
  1523  		AddField(NewField("two", FieldTypeInt64())).
  1524  		AddField(NewField("three", FieldTypeString()).SetNumber(3)).
  1525  		AddField(NewField("four", FieldTypeInt64())).
  1526  		AddField(NewField("five", FieldTypeString()).SetNumber(5))
  1527  
  1528  	md, err := msg.Build()
  1529  	testutil.Ok(t, err)
  1530  
  1531  	testutil.Require(t, md.FindFieldByName("one") != nil)
  1532  	testutil.Eq(t, int32(1), md.FindFieldByName("one").GetNumber())
  1533  
  1534  	testutil.Require(t, md.FindFieldByName("two") != nil)
  1535  	testutil.Eq(t, int32(2), md.FindFieldByName("two").GetNumber())
  1536  
  1537  	testutil.Require(t, md.FindFieldByName("three") != nil)
  1538  	testutil.Eq(t, int32(3), md.FindFieldByName("three").GetNumber())
  1539  
  1540  	testutil.Require(t, md.FindFieldByName("four") != nil)
  1541  	testutil.Eq(t, int32(4), md.FindFieldByName("four").GetNumber())
  1542  
  1543  	testutil.Require(t, md.FindFieldByName("five") != nil)
  1544  	testutil.Eq(t, int32(5), md.FindFieldByName("five").GetNumber())
  1545  }
  1546  
  1547  func clone(t *testing.T, fb *FileBuilder) *FileBuilder {
  1548  	fd, err := fb.Build()
  1549  	testutil.Ok(t, err)
  1550  	fb, err = FromFile(fd)
  1551  	testutil.Ok(t, err)
  1552  	return fb
  1553  }
  1554  
  1555  func TestPruneDependencies(t *testing.T) {
  1556  	msgOpts := &descriptorpb.MessageOptions{}
  1557  	msgOptsDesc, err := desc.LoadMessageDescriptorForMessage(msgOpts)
  1558  	testutil.Ok(t, err)
  1559  	extDesc, err := NewExtensionImported("foo", 20001, FieldTypeString(), msgOptsDesc).Build()
  1560  	testutil.Ok(t, err)
  1561  
  1562  	dm := dynamic.NewMessage(msgOptsDesc)
  1563  	dm.SetField(extDesc, "bar")
  1564  	err = dm.ConvertTo(msgOpts)
  1565  	testutil.Ok(t, err)
  1566  
  1567  	emptyDesc, err := desc.LoadMessageDescriptorForMessage(&emptypb.Empty{})
  1568  	testutil.Ok(t, err)
  1569  
  1570  	// we have to explicitly import the file for the custom option
  1571  	fileB := NewFile("").AddImportedDependency(extDesc.GetFile())
  1572  	msgB := NewMessage("Foo").
  1573  		AddField(NewField("a", FieldTypeImportedMessage(emptyDesc))).
  1574  		SetOptions(msgOpts)
  1575  	fileDesc, err := fileB.AddMessage(msgB).Build()
  1576  	testutil.Ok(t, err)
  1577  
  1578  	// The file for msgDesc should have two imports: one for the custom option and
  1579  	//   one for empty.proto.
  1580  	testutil.Eq(t, 2, len(fileDesc.GetDependencies()))
  1581  	testutil.Eq(t, "google/protobuf/empty.proto", fileDesc.GetDependencies()[0].GetName())
  1582  	testutil.Eq(t, extDesc.GetFile().GetName(), fileDesc.GetDependencies()[1].GetName())
  1583  
  1584  	// If we now remove the message's field, both imports are still there even
  1585  	// though the import for empty.proto is now unused.
  1586  	fileB, err = FromFile(fileDesc)
  1587  	testutil.Ok(t, err)
  1588  	fileB.GetMessage("Foo").RemoveField("a")
  1589  	newFileDesc, err := fileB.Build()
  1590  	testutil.Ok(t, err)
  1591  	testutil.Eq(t, 2, len(newFileDesc.GetDependencies()))
  1592  	testutil.Eq(t, "google/protobuf/empty.proto", newFileDesc.GetDependencies()[0].GetName())
  1593  	testutil.Eq(t, extDesc.GetFile().GetName(), newFileDesc.GetDependencies()[1].GetName())
  1594  
  1595  	// But if we prune unused dependencies, we'll see the import for empty.proto
  1596  	// gone. The other import for the custom option should be preserved.
  1597  	fileB, err = FromFile(fileDesc)
  1598  	testutil.Ok(t, err)
  1599  	fileB.GetMessage("Foo").RemoveField("a")
  1600  	newFileDesc, err = fileB.PruneUnusedDependencies().Build()
  1601  	testutil.Ok(t, err)
  1602  	testutil.Eq(t, 1, len(newFileDesc.GetDependencies()))
  1603  	testutil.Eq(t, extDesc.GetFile().GetName(), newFileDesc.GetDependencies()[0].GetName())
  1604  }
  1605  
  1606  func TestInterleavedEnumNumbers(t *testing.T) {
  1607  	en := NewEnum("Options").
  1608  		AddValue(NewEnumValue("OPTION_1").SetNumber(-1)).
  1609  		AddValue(NewEnumValue("OPTION_2")).
  1610  		AddValue(NewEnumValue("OPTION_3").SetNumber(2)).
  1611  		AddValue(NewEnumValue("OPTION_4").SetNumber(1)).
  1612  		AddValue(NewEnumValue("OPTION_5")).
  1613  		AddValue(NewEnumValue("OPTION_6").SetNumber(100))
  1614  
  1615  	ed, err := en.Build()
  1616  	testutil.Ok(t, err)
  1617  
  1618  	testutil.Require(t, ed.FindValueByName("OPTION_1") != nil)
  1619  	testutil.Eq(t, int32(-1), ed.FindValueByName("OPTION_1").GetNumber())
  1620  
  1621  	testutil.Require(t, ed.FindValueByName("OPTION_2") != nil)
  1622  	testutil.Eq(t, int32(0), ed.FindValueByName("OPTION_2").GetNumber())
  1623  
  1624  	testutil.Require(t, ed.FindValueByName("OPTION_3") != nil)
  1625  	testutil.Eq(t, int32(2), ed.FindValueByName("OPTION_3").GetNumber())
  1626  
  1627  	testutil.Require(t, ed.FindValueByName("OPTION_4") != nil)
  1628  	testutil.Eq(t, int32(1), ed.FindValueByName("OPTION_4").GetNumber())
  1629  
  1630  	testutil.Require(t, ed.FindValueByName("OPTION_5") != nil)
  1631  	testutil.Eq(t, int32(3), ed.FindValueByName("OPTION_5").GetNumber())
  1632  
  1633  	testutil.Require(t, ed.FindValueByName("OPTION_6") != nil)
  1634  	testutil.Eq(t, int32(100), ed.FindValueByName("OPTION_6").GetNumber())
  1635  }
  1636  
  1637  func TestInvalid(t *testing.T) {
  1638  	testCases := []struct {
  1639  		name          string
  1640  		builder       func() Builder
  1641  		expectedError string
  1642  	}{
  1643  		{
  1644  			name: "required in proto3",
  1645  			builder: func() Builder {
  1646  				return NewFile("foo.proto").SetProto3(true).
  1647  					AddMessage(
  1648  						NewMessage("Foo").AddField(NewField("foo", FieldTypeBool()).SetRequired()),
  1649  					)
  1650  			},
  1651  			expectedError: "only proto2 allows required fields",
  1652  		},
  1653  		{
  1654  			name: "extension range in proto3",
  1655  			builder: func() Builder {
  1656  				return NewFile("foo.proto").SetProto3(true).
  1657  					AddMessage(
  1658  						NewMessage("Foo").AddExtensionRange(100, 1000),
  1659  					)
  1660  			},
  1661  			expectedError: "proto3 semantics cannot have extension ranges",
  1662  		},
  1663  		{
  1664  			name: "group in proto3",
  1665  			builder: func() Builder {
  1666  				return NewFile("foo.proto").SetProto3(true).
  1667  					AddMessage(
  1668  						NewMessage("Foo").AddField(NewGroupField(NewMessage("Bar"))),
  1669  					)
  1670  			},
  1671  			expectedError: "invalid group: invalid under proto3 semantics",
  1672  		},
  1673  		{
  1674  			name: "default value in proto3",
  1675  			builder: func() Builder {
  1676  				return NewFile("foo.proto").SetProto3(true).
  1677  					AddMessage(
  1678  						NewMessage("Foo").AddField(NewField("foo", FieldTypeString()).SetDefaultValue("abc")),
  1679  					)
  1680  			},
  1681  			expectedError: "invalid default: cannot be specified with implicit field presence",
  1682  		},
  1683  		{
  1684  			name: "extension tag outside range",
  1685  			builder: func() Builder {
  1686  				msg := NewMessage("Foo").AddExtensionRange(100, 1000)
  1687  				return NewFile("foo.proto").
  1688  					AddMessage(msg).
  1689  					AddExtension(NewExtension("foo", 1, FieldTypeString(), msg))
  1690  			},
  1691  			expectedError: "non-extension field number: 1",
  1692  		},
  1693  		{
  1694  			name: "non-extension tag in extension range",
  1695  			builder: func() Builder {
  1696  				return NewFile("foo.proto").
  1697  					AddMessage(NewMessage("Foo").
  1698  						AddField(NewField("foo", FieldTypeBool()).SetNumber(100)).
  1699  						AddExtensionRange(100, 1000))
  1700  			},
  1701  			expectedError: "number 100 in extension range",
  1702  		},
  1703  		{
  1704  			name: "tag in reserved range",
  1705  			builder: func() Builder {
  1706  				return NewFile("foo.proto").
  1707  					AddMessage(NewMessage("Foo").
  1708  						AddField(NewField("foo", FieldTypeBool()).SetNumber(100)).
  1709  						AddReservedRange(100, 1000))
  1710  			},
  1711  			expectedError: "must not use reserved number 100",
  1712  		},
  1713  		{
  1714  			name: "field has reserved name",
  1715  			builder: func() Builder {
  1716  				return NewFile("foo.proto").
  1717  					AddMessage(NewMessage("Foo").
  1718  						AddField(NewField("foo", FieldTypeBool())).
  1719  						AddReservedName("foo"))
  1720  			},
  1721  			expectedError: "must not use reserved name",
  1722  		},
  1723  		{
  1724  			name: "ranges overlap",
  1725  			builder: func() Builder {
  1726  				return NewFile("foo.proto").
  1727  					AddMessage(NewMessage("Foo").
  1728  						AddReservedRange(100, 1000).
  1729  						AddExtensionRange(200, 300))
  1730  			},
  1731  			expectedError: "reserved and extension ranges has overlapping ranges",
  1732  		},
  1733  	}
  1734  	for _, testCase := range testCases {
  1735  		t.Run(testCase.name, func(t *testing.T) {
  1736  			_, err := testCase.builder().BuildDescriptor()
  1737  			testutil.Nok(t, err)
  1738  			testutil.Require(t, strings.Contains(err.Error(), testCase.expectedError), "unexpected error: want %q, got %q", testCase.expectedError, err.Error())
  1739  		})
  1740  	}
  1741  }