github.com/Big-big-orange/protoreflect@v0.0.0-20240408141420-285cedfdf6a4/desc/protoparse/validate_test.go (about)

     1  package protoparse
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/Big-big-orange/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 in enum have numeric value of 0`,
   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  }