github.com/hoveychen/protoreflect@v1.4.7-0.20221103114119-0b4b3385ec76/desc/protoparse/options_test.go (about)

     1  package protoparse
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  
    11  	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    12  	"github.com/hoveychen/protoreflect/internal/testutil"
    13  )
    14  
    15  type ident string
    16  type aggregate string
    17  
    18  func TestOptionsInUnlinkedFiles(t *testing.T) {
    19  	testCases := []struct {
    20  		contents         string
    21  		uninterpreted    map[string]interface{}
    22  		checkInterpreted func(*testing.T, *dpb.FileDescriptorProto)
    23  	}{
    24  		{
    25  			// file options
    26  			contents: `option go_package = "foo.bar"; option (must.link) = "FOO";`,
    27  			uninterpreted: map[string]interface{}{
    28  				"test.proto:(must.link)": "FOO",
    29  			},
    30  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
    31  				testutil.Eq(t, "foo.bar", fd.GetOptions().GetGoPackage())
    32  			},
    33  		},
    34  		{
    35  			// message options
    36  			contents: `message Test { option (must.link) = 1.234; option deprecated = true; }`,
    37  			uninterpreted: map[string]interface{}{
    38  				"Test:(must.link)": 1.234,
    39  			},
    40  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
    41  				testutil.Require(t, fd.GetMessageType()[0].GetOptions().GetDeprecated())
    42  			},
    43  		},
    44  		{
    45  			// field options and pseudo-options
    46  			contents: `message Test { optional string uid = 1 [(must.link) = 10101, (must.link) = 20202, default = "fubar", json_name = "UID", deprecated = true]; }`,
    47  			uninterpreted: map[string]interface{}{
    48  				"Test.uid:(must.link)":   10101,
    49  				"Test.uid:(must.link)#1": 20202,
    50  			},
    51  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
    52  				testutil.Eq(t, "fubar", fd.GetMessageType()[0].GetField()[0].GetDefaultValue())
    53  				testutil.Eq(t, "UID", fd.GetMessageType()[0].GetField()[0].GetJsonName())
    54  				testutil.Require(t, fd.GetMessageType()[0].GetField()[0].GetOptions().GetDeprecated())
    55  			},
    56  		},
    57  		{
    58  			// field where default is uninterpretable
    59  			contents: `enum TestEnum{ ZERO = 0; ONE = 1; } message Test { optional TestEnum uid = 1 [(must.link) = {foo: bar}, default = ONE, json_name = "UID", deprecated = true]; }`,
    60  			uninterpreted: map[string]interface{}{
    61  				"Test.uid:(must.link)": aggregate("{ foo: bar }"),
    62  				"Test.uid:default":     ident("ONE"),
    63  			},
    64  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
    65  				testutil.Eq(t, "UID", fd.GetMessageType()[0].GetField()[0].GetJsonName())
    66  				testutil.Require(t, fd.GetMessageType()[0].GetField()[0].GetOptions().GetDeprecated())
    67  			},
    68  		},
    69  		{
    70  			// one-of options
    71  			contents: `message Test { oneof x { option (must.link) = true; option deprecated = true; string uid = 1; uint64 nnn = 2; } }`,
    72  			uninterpreted: map[string]interface{}{
    73  				"Test.x:(must.link)": ident("true"),
    74  				"Test.x:deprecated":  ident("true"), // one-ofs do not have deprecated option :/
    75  			},
    76  		},
    77  		{
    78  			// extension range options
    79  			contents: `message Test { extensions 100 to 200 [(must.link) = "foo", deprecated = true]; }`,
    80  			uninterpreted: map[string]interface{}{
    81  				"Test.100-200:(must.link)": "foo",
    82  				"Test.100-200:deprecated":  ident("true"), // extension ranges do not have deprecated option :/
    83  			},
    84  		},
    85  		{
    86  			// enum options
    87  			contents: `enum Test { option allow_alias = true; option deprecated = true; option (must.link) = 123.456; ZERO = 0; }`,
    88  			uninterpreted: map[string]interface{}{
    89  				"Test:(must.link)": 123.456,
    90  			},
    91  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
    92  				testutil.Require(t, fd.GetEnumType()[0].GetOptions().GetDeprecated())
    93  				testutil.Require(t, fd.GetEnumType()[0].GetOptions().GetAllowAlias())
    94  			},
    95  		},
    96  		{
    97  			// enum value options
    98  			contents: `enum Test { ZERO = 0 [deprecated = true, (must.link) = -222]; }`,
    99  			uninterpreted: map[string]interface{}{
   100  				"Test.ZERO:(must.link)": -222,
   101  			},
   102  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
   103  				testutil.Require(t, fd.GetEnumType()[0].GetValue()[0].GetOptions().GetDeprecated())
   104  			},
   105  		},
   106  		{
   107  			// service options
   108  			contents: `service Test { option deprecated = true; option (must.link) = {foo:1, foo:2, bar:3}; }`,
   109  			uninterpreted: map[string]interface{}{
   110  				"Test:(must.link)": aggregate("{ foo: 1 foo: 2 bar: 3 }"),
   111  			},
   112  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
   113  				testutil.Require(t, fd.GetService()[0].GetOptions().GetDeprecated())
   114  			},
   115  		},
   116  		{
   117  			// method options
   118  			contents: `import "google/protobuf/empty.proto"; service Test { rpc Foo (google.protobuf.Empty) returns (google.protobuf.Empty) { option deprecated = true; option (must.link) = FOO; } }`,
   119  			uninterpreted: map[string]interface{}{
   120  				"Test.Foo:(must.link)": ident("FOO"),
   121  			},
   122  			checkInterpreted: func(t *testing.T, fd *dpb.FileDescriptorProto) {
   123  				testutil.Require(t, fd.GetService()[0].GetMethod()[0].GetOptions().GetDeprecated())
   124  			},
   125  		},
   126  	}
   127  
   128  	for i, tc := range testCases {
   129  		p := Parser{
   130  			Accessor:                        accessorFor("test.proto", tc.contents),
   131  			ValidateUnlinkedFiles:           true,
   132  			InterpretOptionsInUnlinkedFiles: true,
   133  		}
   134  		fds, err := p.ParseFilesButDoNotLink("test.proto")
   135  		testutil.Ok(t, err, "case #%d failed to parse", i)
   136  		testutil.Eq(t, 1, len(fds), "case #%d produced wrong number of descriptor protos", i)
   137  		actual := map[string]interface{}{}
   138  		buildUninterpretedMapForFile(fds[0], actual)
   139  		testutil.Eq(t, tc.uninterpreted, actual, "case #%d resulted in wrong uninterpreted options", i)
   140  		if tc.checkInterpreted != nil {
   141  			tc.checkInterpreted(t, fds[0])
   142  		}
   143  	}
   144  }
   145  
   146  func accessorFor(name, contents string) FileAccessor {
   147  	return func(n string) (io.ReadCloser, error) {
   148  		if n == name {
   149  			return ioutil.NopCloser(strings.NewReader(contents)), nil
   150  		}
   151  		return nil, os.ErrNotExist
   152  	}
   153  }
   154  
   155  func buildUninterpretedMapForFile(fd *dpb.FileDescriptorProto, opts map[string]interface{}) {
   156  	buildUninterpretedMap(fd.GetName(), fd.GetOptions().GetUninterpretedOption(), opts)
   157  	for _, md := range fd.GetMessageType() {
   158  		buildUninterpretedMapForMessage(fd.GetPackage(), md, opts)
   159  	}
   160  	for _, extd := range fd.GetExtension() {
   161  		buildUninterpretedMap(qualify(fd.GetPackage(), extd.GetName()), extd.GetOptions().GetUninterpretedOption(), opts)
   162  	}
   163  	for _, ed := range fd.GetEnumType() {
   164  		buildUninterpretedMapForEnum(fd.GetPackage(), ed, opts)
   165  	}
   166  	for _, sd := range fd.GetService() {
   167  		svcFqn := qualify(fd.GetPackage(), sd.GetName())
   168  		buildUninterpretedMap(svcFqn, sd.GetOptions().GetUninterpretedOption(), opts)
   169  		for _, mtd := range sd.GetMethod() {
   170  			buildUninterpretedMap(qualify(svcFqn, mtd.GetName()), mtd.GetOptions().GetUninterpretedOption(), opts)
   171  		}
   172  	}
   173  }
   174  
   175  func buildUninterpretedMapForMessage(qual string, md *dpb.DescriptorProto, opts map[string]interface{}) {
   176  	fqn := qualify(qual, md.GetName())
   177  	buildUninterpretedMap(fqn, md.GetOptions().GetUninterpretedOption(), opts)
   178  	for _, fld := range md.GetField() {
   179  		buildUninterpretedMap(qualify(fqn, fld.GetName()), fld.GetOptions().GetUninterpretedOption(), opts)
   180  	}
   181  	for _, ood := range md.GetOneofDecl() {
   182  		buildUninterpretedMap(qualify(fqn, ood.GetName()), ood.GetOptions().GetUninterpretedOption(), opts)
   183  	}
   184  	for _, extr := range md.GetExtensionRange() {
   185  		buildUninterpretedMap(qualify(fqn, fmt.Sprintf("%d-%d", extr.GetStart(), extr.GetEnd()-1)), extr.GetOptions().GetUninterpretedOption(), opts)
   186  	}
   187  	for _, nmd := range md.GetNestedType() {
   188  		buildUninterpretedMapForMessage(fqn, nmd, opts)
   189  	}
   190  	for _, extd := range md.GetExtension() {
   191  		buildUninterpretedMap(qualify(fqn, extd.GetName()), extd.GetOptions().GetUninterpretedOption(), opts)
   192  	}
   193  	for _, ed := range md.GetEnumType() {
   194  		buildUninterpretedMapForEnum(fqn, ed, opts)
   195  	}
   196  }
   197  
   198  func buildUninterpretedMapForEnum(qual string, ed *dpb.EnumDescriptorProto, opts map[string]interface{}) {
   199  	fqn := qualify(qual, ed.GetName())
   200  	buildUninterpretedMap(fqn, ed.GetOptions().GetUninterpretedOption(), opts)
   201  	for _, evd := range ed.GetValue() {
   202  		buildUninterpretedMap(qualify(fqn, evd.GetName()), evd.GetOptions().GetUninterpretedOption(), opts)
   203  	}
   204  }
   205  
   206  func buildUninterpretedMap(prefix string, uos []*dpb.UninterpretedOption, opts map[string]interface{}) {
   207  	for _, uo := range uos {
   208  		parts := make([]string, len(uo.GetName()))
   209  		for i, np := range uo.GetName() {
   210  			if np.GetIsExtension() {
   211  				parts[i] = fmt.Sprintf("(%s)", np.GetNamePart())
   212  			} else {
   213  				parts[i] = np.GetNamePart()
   214  			}
   215  		}
   216  		uoName := fmt.Sprintf("%s:%s", prefix, strings.Join(parts, "."))
   217  		key := uoName
   218  		i := 0
   219  		for {
   220  			if _, ok := opts[key]; !ok {
   221  				break
   222  			}
   223  			i++
   224  			key = fmt.Sprintf("%s#%d", uoName, i)
   225  		}
   226  		var val interface{}
   227  		switch {
   228  		case uo.AggregateValue != nil:
   229  			val = aggregate(uo.GetAggregateValue())
   230  		case uo.IdentifierValue != nil:
   231  			val = ident(uo.GetIdentifierValue())
   232  		case uo.DoubleValue != nil:
   233  			val = uo.GetDoubleValue()
   234  		case uo.PositiveIntValue != nil:
   235  			val = int(uo.GetPositiveIntValue())
   236  		case uo.NegativeIntValue != nil:
   237  			val = int(uo.GetNegativeIntValue())
   238  		default:
   239  			val = string(uo.GetStringValue())
   240  		}
   241  		opts[key] = val
   242  	}
   243  }