github.com/jhump/protoreflect@v1.16.0/desc/protoparse/options_test.go (about) 1 package protoparse 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "strings" 8 "testing" 9 10 "google.golang.org/protobuf/types/descriptorpb" 11 12 "github.com/jhump/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, *descriptorpb.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 *descriptorpb.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 *descriptorpb.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 *descriptorpb.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 *descriptorpb.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; ZILCH = 0; }`, 88 uninterpreted: map[string]interface{}{ 89 "Test:(must.link)": 123.456, 90 }, 91 checkInterpreted: func(t *testing.T, fd *descriptorpb.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 *descriptorpb.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 *descriptorpb.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 *descriptorpb.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 io.NopCloser(strings.NewReader(contents)), nil 150 } 151 return nil, os.ErrNotExist 152 } 153 } 154 155 func buildUninterpretedMapForFile(fd *descriptorpb.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 *descriptorpb.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 *descriptorpb.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 []*descriptorpb.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 } 244 245 func qualify(base, name string) string { 246 if base == "" { 247 return name 248 } 249 return base + "." + name 250 }