github.com/phpstudyer/protoreflect@v1.7.2/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/phpstudyer/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; }`, 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 }