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  }