github.com/bakjos/protoreflect@v1.9.2/desc/protoparse/validate_test.go (about) 1 package protoparse 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/bakjos/protoreflect/internal/testutil" 8 ) 9 10 func TestBasicValidation(t *testing.T) { 11 testCases := []struct { 12 contents string 13 succeeds bool 14 errMsg string 15 }{ 16 { 17 contents: `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`, 18 succeeds: true, 19 }, 20 { 21 // with byte order marker 22 contents: string([]byte{0xEF, 0xBB, 0xBF}) + `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`, 23 succeeds: true, 24 }, 25 { 26 contents: `message Foo { optional double bar = 1 [default = 18446744073709551616]; }`, 27 succeeds: true, 28 }, 29 { 30 contents: `message Foo { oneof bar { group Baz = 1 [deprecated=true] { optional int abc = 1; } } }`, 31 succeeds: true, 32 }, 33 { 34 contents: `message Foo { option message_set_wire_format = true; extensions 1 to 100; }`, 35 succeeds: true, 36 }, 37 { 38 contents: `message Foo { optional double bar = 536870912; option message_set_wire_format = true; }`, 39 errMsg: "test.proto:1:15: messages with message-set wire format cannot contain non-extension fields", 40 }, 41 { 42 contents: `message Foo { option message_set_wire_format = true; }`, 43 errMsg: "test.proto:1:15: messages with message-set wire format must contain at least one extension range", 44 }, 45 { 46 contents: `syntax = "proto1";`, 47 errMsg: `test.proto:1:10: syntax value must be "proto2" or "proto3"`, 48 }, 49 { 50 contents: `message Foo { optional string s = 5000000000; }`, 51 errMsg: `test.proto:1:35: tag number 5000000000 is higher than max allowed tag number (536870911)`, 52 }, 53 { 54 contents: `message Foo { optional string s = 19500; }`, 55 errMsg: `test.proto:1:35: tag number 19500 is in disallowed reserved range 19000-19999`, 56 }, 57 { 58 contents: `enum Foo { V = 5000000000; }`, 59 errMsg: `test.proto:1:16: value 5000000000 is out of range: should be between -2147483648 and 2147483647`, 60 }, 61 { 62 contents: `enum Foo { V = -5000000000; }`, 63 errMsg: `test.proto:1:16: value -5000000000 is out of range: should be between -2147483648 and 2147483647`, 64 }, 65 { 66 contents: `enum Foo { V = 0; reserved 5000000000; }`, 67 errMsg: `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`, 68 }, 69 { 70 contents: `enum Foo { V = 0; reserved -5000000000; }`, 71 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 72 }, 73 { 74 contents: `enum Foo { V = 0; reserved 5000000000 to 5000000001; }`, 75 errMsg: `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`, 76 }, 77 { 78 contents: `enum Foo { V = 0; reserved 5 to 5000000000; }`, 79 errMsg: `test.proto:1:33: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`, 80 }, 81 { 82 contents: `enum Foo { V = 0; reserved -5000000000 to -5; }`, 83 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 84 }, 85 { 86 contents: `enum Foo { V = 0; reserved -5000000001 to -5000000000; }`, 87 errMsg: `test.proto:1:28: range start -5000000001 is out of range: should be between -2147483648 and 2147483647`, 88 }, 89 { 90 contents: `enum Foo { V = 0; reserved -5000000000 to 5; }`, 91 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 92 }, 93 { 94 contents: `enum Foo { V = 0; reserved -5 to 5000000000; }`, 95 errMsg: `test.proto:1:34: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`, 96 }, 97 { 98 contents: `enum Foo { }`, 99 errMsg: `test.proto:1:1: enum Foo: enums must define at least one value`, 100 }, 101 { 102 contents: `message Foo { oneof Bar { } }`, 103 errMsg: `test.proto:1:15: oneof must contain at least one field`, 104 }, 105 { 106 contents: `message Foo { extensions 1 to max; } extend Foo { }`, 107 errMsg: `test.proto:1:38: extend sections must define at least one extension`, 108 }, 109 { 110 contents: `message Foo { option map_entry = true; }`, 111 errMsg: `test.proto:1:34: message Foo: map_entry option should not be set explicitly; use map type instead`, 112 }, 113 { 114 contents: `message Foo { option map_entry = false; }`, 115 succeeds: true, // okay if explicit setting is false 116 }, 117 { 118 contents: `syntax = "proto2"; message Foo { string s = 1; }`, 119 errMsg: `test.proto:1:41: field Foo.s: field has no label; proto2 requires explicit 'optional' label`, 120 }, 121 { 122 contents: `message Foo { string s = 1; }`, // syntax defaults to proto2 123 errMsg: `test.proto:1:22: field Foo.s: field has no label; proto2 requires explicit 'optional' label`, 124 }, 125 { 126 contents: `syntax = "proto3"; message Foo { optional string s = 1; }`, 127 succeeds: true, // proto3_optional 128 }, 129 { 130 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; extend google.protobuf.MessageOptions { optional string s = 50000; }`, 131 succeeds: true, // proto3_optional for extensions 132 }, 133 { 134 contents: `syntax = "proto3"; message Foo { required string s = 1; }`, 135 errMsg: `test.proto:1:34: field Foo.s: label 'required' is not allowed in proto3`, 136 }, 137 { 138 contents: `message Foo { extensions 1 to max; } extend Foo { required string sss = 100; }`, 139 errMsg: `test.proto:1:51: field sss: extension fields cannot be 'required'`, 140 }, 141 { 142 contents: `syntax = "proto3"; message Foo { optional group Grp = 1 { } }`, 143 errMsg: `test.proto:1:43: field Foo.grp: groups are not allowed in proto3`, 144 }, 145 { 146 contents: `syntax = "proto3"; message Foo { extensions 1 to max; }`, 147 errMsg: `test.proto:1:45: message Foo: extension ranges are not allowed in proto3`, 148 }, 149 { 150 contents: `syntax = "proto3"; message Foo { string s = 1 [default = "abcdef"]; }`, 151 errMsg: `test.proto:1:48: field Foo.s: default values are not allowed in proto3`, 152 }, 153 { 154 contents: `enum Foo { V1 = 1; V2 = 1; }`, 155 errMsg: `test.proto:1:25: enum Foo: values V1 and V2 both have the same numeric value 1; use allow_alias option if intentional`, 156 }, 157 { 158 contents: `enum Foo { option allow_alias = true; V1 = 1; V2 = 1; }`, 159 succeeds: true, 160 }, 161 { 162 contents: `enum Foo { option allow_alias = false; V1 = 1; V2 = 2; }`, 163 succeeds: true, 164 }, 165 { 166 contents: `enum Foo { option allow_alias = true; V1 = 1; V2 = 2; }`, 167 errMsg: `test.proto:1:33: enum Foo: allow_alias is true but no values are aliases`, 168 }, 169 { 170 contents: `syntax = "proto3"; enum Foo { V1 = 0; reserved 1 to 20; reserved "V2"; }`, 171 succeeds: true, 172 }, 173 { 174 contents: `enum Foo { V1 = 1; reserved 1 to 20; reserved "V2"; }`, 175 errMsg: `test.proto:1:17: enum Foo: value V1 is using number 1 which is in reserved range 1 to 20`, 176 }, 177 { 178 contents: `enum Foo { V1 = 20; reserved 1 to 20; reserved "V2"; }`, 179 errMsg: `test.proto:1:17: enum Foo: value V1 is using number 20 which is in reserved range 1 to 20`, 180 }, 181 { 182 contents: `enum Foo { V2 = 0; reserved 1 to 20; reserved "V2"; }`, 183 errMsg: `test.proto:1:12: enum Foo: value V2 is using a reserved name`, 184 }, 185 { 186 contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 21 to 40; reserved "V2"; }`, 187 succeeds: true, 188 }, 189 { 190 contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 20 to 40; reserved "V2"; }`, 191 errMsg: `test.proto:1:47: enum Foo: reserved ranges overlap: 1 to 20 and 20 to 40`, 192 }, 193 { 194 contents: `syntax = "proto3"; enum Foo { FIRST = 1; }`, 195 errMsg: `test.proto:1:39: enum Foo: proto3 requires that first value in enum have numeric value of 0`, 196 }, 197 { 198 contents: `syntax = "proto3"; message Foo { string s = 1; int32 i = 1; }`, 199 errMsg: `test.proto:1:58: message Foo: fields s and i both have the same tag 1`, 200 }, 201 { 202 contents: `message Foo { reserved 1 to 10, 10 to 12; }`, 203 errMsg: `test.proto:1:33: message Foo: reserved ranges overlap: 1 to 10 and 10 to 12`, 204 }, 205 { 206 contents: `message Foo { extensions 1 to 10, 10 to 12; }`, 207 errMsg: `test.proto:1:35: message Foo: extension ranges overlap: 1 to 10 and 10 to 12`, 208 }, 209 { 210 contents: `message Foo { reserved 1 to 10; extensions 10 to 12; }`, 211 errMsg: `test.proto:1:44: message Foo: extension range 10 to 12 overlaps reserved range 1 to 10`, 212 }, 213 { 214 contents: `message Foo { reserved 1, 2 to 10, 11 to 20; extensions 21 to 22; }`, 215 succeeds: true, 216 }, 217 { 218 contents: `message Foo { reserved 10 to 1; }`, 219 errMsg: `test.proto:1:24: range, 10 to 1, is invalid: start must be <= end`, 220 }, 221 { 222 contents: `message Foo { extensions 10 to 1; }`, 223 errMsg: `test.proto:1:26: range, 10 to 1, is invalid: start must be <= end`, 224 }, 225 { 226 contents: `message Foo { reserved 1 to 5000000000; }`, 227 errMsg: `test.proto:1:29: range end 5000000000 is out of range: should be between 0 and 536870911`, 228 }, 229 { 230 contents: `message Foo { extensions 3000000000; }`, 231 errMsg: `test.proto:1:26: range start 3000000000 is out of range: should be between 0 and 536870911`, 232 }, 233 { 234 contents: `message Foo { extensions 3000000000 to 3000000001; }`, 235 errMsg: `test.proto:1:26: range start 3000000000 is out of range: should be between 0 and 536870911`, 236 }, 237 { 238 contents: `message Foo { extensions 100 to 3000000000; }`, 239 errMsg: `test.proto:1:33: range end 3000000000 is out of range: should be between 0 and 536870911`, 240 }, 241 { 242 contents: `message Foo { reserved "foo", "foo"; }`, 243 errMsg: `test.proto:1:31: name "foo" is reserved multiple times`, 244 }, 245 { 246 contents: `message Foo { reserved "foo"; reserved "foo"; }`, 247 errMsg: `test.proto:1:40: name "foo" is reserved multiple times`, 248 }, 249 { 250 contents: `message Foo { reserved "foo"; optional string foo = 1; }`, 251 errMsg: `test.proto:1:47: message Foo: field foo is using a reserved name`, 252 }, 253 { 254 contents: `message Foo { reserved 1 to 10; optional string foo = 1; }`, 255 errMsg: `test.proto:1:55: message Foo: field foo is using tag 1 which is in reserved range 1 to 10`, 256 }, 257 { 258 contents: `message Foo { extensions 1 to 10; optional string foo = 1; }`, 259 errMsg: `test.proto:1:57: message Foo: field foo is using tag 1 which is in extension range 1 to 10`, 260 }, 261 { 262 contents: `message Foo { optional group foo = 1 { } }`, 263 errMsg: `test.proto:1:30: group foo should have a name that starts with a capital letter`, 264 }, 265 { 266 contents: `message Foo { oneof foo { group bar = 1 { } } }`, 267 errMsg: `test.proto:1:33: group bar should have a name that starts with a capital letter`, 268 }, 269 { 270 contents: `enum Foo { option = 1; }`, 271 errMsg: `test.proto:1:19: syntax error: unexpected '='`, 272 }, 273 { 274 contents: `enum Foo { reserved = 1; }`, 275 errMsg: `test.proto:1:21: syntax error: unexpected '=', expecting string literal or int literal or '-'`, 276 }, 277 { 278 contents: `syntax = "proto3"; enum message { unset = 0; } message Foo { message bar = 1; }`, 279 errMsg: `test.proto:1:74: syntax error: unexpected '=', expecting '{'`, 280 }, 281 { 282 contents: `syntax = "proto3"; enum enum { unset = 0; } message Foo { enum bar = 1; }`, 283 errMsg: `test.proto:1:68: syntax error: unexpected '=', expecting '{'`, 284 }, 285 { 286 contents: `syntax = "proto3"; enum reserved { unset = 0; } message Foo { reserved bar = 1; }`, 287 errMsg: `test.proto:1:72: syntax error: unexpected identifier, expecting string literal or int literal`, 288 }, 289 { 290 contents: `syntax = "proto3"; enum extend { unset = 0; } message Foo { extend bar = 1; }`, 291 errMsg: `test.proto:1:72: syntax error: unexpected '=', expecting '{'`, 292 }, 293 { 294 contents: `syntax = "proto3"; enum oneof { unset = 0; } message Foo { oneof bar = 1; }`, 295 errMsg: `test.proto:1:70: syntax error: unexpected '=', expecting '{'`, 296 }, 297 { 298 contents: `syntax = "proto3"; enum optional { unset = 0; } message Foo { optional bar = 1; }`, 299 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 300 }, 301 { 302 contents: `syntax = "proto3"; enum repeated { unset = 0; } message Foo { repeated bar = 1; }`, 303 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 304 }, 305 { 306 contents: `syntax = "proto3"; enum required { unset = 0; } message Foo { required bar = 1; }`, 307 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 308 }, 309 { 310 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum optional { unset = 0; } extend google.protobuf.MethodOptions { optional bar = 22222; }`, 311 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 312 }, 313 { 314 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum repeated { unset = 0; } extend google.protobuf.MethodOptions { repeated bar = 22222; }`, 315 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 316 }, 317 { 318 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum required { unset = 0; } extend google.protobuf.MethodOptions { required bar = 22222; }`, 319 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 320 }, 321 { 322 contents: `syntax = "proto3"; enum optional { unset = 0; } message Foo { oneof bar { optional bar = 1; } }`, 323 errMsg: `test.proto:1:75: syntax error: unexpected "optional"`, 324 }, 325 { 326 contents: `syntax = "proto3"; enum repeated { unset = 0; } message Foo { oneof bar { repeated bar = 1; } }`, 327 errMsg: `test.proto:1:75: syntax error: unexpected "repeated"`, 328 }, 329 { 330 contents: `syntax = "proto3"; enum required { unset = 0; } message Foo { oneof bar { required bar = 1; } }`, 331 errMsg: `test.proto:1:75: syntax error: unexpected "required"`, 332 }, 333 { 334 contents: ``, 335 succeeds: true, 336 }, 337 { 338 contents: `0`, 339 errMsg: `test.proto:1:1: syntax error: unexpected int literal`, 340 }, 341 { 342 contents: `foobar`, 343 errMsg: `test.proto:1:1: syntax error: unexpected identifier`, 344 }, 345 { 346 contents: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`, 347 errMsg: `test.proto:1:1: syntax error: unexpected identifier`, 348 }, 349 { 350 contents: `"abc"`, 351 errMsg: `test.proto:1:1: syntax error: unexpected string literal`, 352 }, 353 { 354 contents: `0.0.0.0.0`, 355 errMsg: `test.proto:1:1: syntax error: unexpected float literal`, 356 }, 357 } 358 359 for i, tc := range testCases { 360 errs := newErrorHandler(nil, nil) 361 _ = parseProto("test.proto", strings.NewReader(tc.contents), errs, true, true) 362 err := errs.getError() 363 if tc.succeeds { 364 testutil.Ok(t, err, "case #%d should succeed", i) 365 } else { 366 testutil.Nok(t, err, "case #%d should fail", i) 367 testutil.Eq(t, tc.errMsg, err.Error(), "case #%d bad error message", i) 368 } 369 } 370 }