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