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