Skip to content

Commit aabae75

Browse files
authored
Add field_mask rules const/in/not_in (#429)
This implements #428. I might require some help regarding the test suites. Let me know what you think. Signed-off-by: Christoph Hoopmann <[email protected]>
1 parent c078a54 commit aabae75

File tree

8 files changed

+940
-113
lines changed

8 files changed

+940
-113
lines changed

proto/protovalidate-testing/buf/validate/conformance/cases/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ proto_library(
4444
"strings.proto",
4545
"wkt_any.proto",
4646
"wkt_duration.proto",
47+
"wkt_field_mask.proto",
4748
"wkt_nested.proto",
4849
"wkt_timestamp.proto",
4950
"wkt_wrappers.proto",
@@ -56,6 +57,7 @@ proto_library(
5657
"//proto/protovalidate/buf/validate:validate_proto",
5758
"@com_google_protobuf//:any_proto",
5859
"@com_google_protobuf//:duration_proto",
60+
"@com_google_protobuf//:field_mask_proto",
5961
"@com_google_protobuf//:timestamp_proto",
6062
"@com_google_protobuf//:wrappers_proto",
6163
],
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2023-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package buf.validate.conformance.cases;
18+
19+
import "buf/validate/validate.proto";
20+
import "google/protobuf/field_mask.proto";
21+
22+
message FieldMaskNone {
23+
google.protobuf.FieldMask val = 1;
24+
}
25+
message FieldMaskRequired {
26+
google.protobuf.FieldMask val = 1 [(buf.validate.field).required = true];
27+
}
28+
29+
message FieldMaskConst {
30+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask.const = {
31+
paths: ["a"]
32+
}];
33+
}
34+
35+
message FieldMaskIn {
36+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
37+
in: [
38+
"a",
39+
"b"
40+
]
41+
}];
42+
}
43+
message FieldMaskNotIn {
44+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
45+
not_in: [
46+
"c",
47+
"d"
48+
]
49+
}];
50+
}
51+
52+
message FieldMaskExample {
53+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask.example = {
54+
paths: ["a"]
55+
}];
56+
}

proto/protovalidate/buf/validate/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ proto_library(
2525
deps = [
2626
"@com_google_protobuf//:descriptor_proto",
2727
"@com_google_protobuf//:duration_proto",
28+
"@com_google_protobuf//:field_mask_proto",
2829
"@com_google_protobuf//:timestamp_proto",
2930
],
3031
)

proto/protovalidate/buf/validate/validate.proto

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package buf.validate;
1818

1919
import "google/protobuf/descriptor.proto";
2020
import "google/protobuf/duration.proto";
21+
import "google/protobuf/field_mask.proto";
2122
import "google/protobuf/timestamp.proto";
2223

2324
option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate";
@@ -313,6 +314,7 @@ message FieldRules {
313314
// Well-Known Field Types
314315
AnyRules any = 20;
315316
DurationRules duration = 21;
317+
FieldMaskRules field_mask = 28;
316318
TimestampRules timestamp = 22;
317319
}
318320

@@ -4630,6 +4632,93 @@ message DurationRules {
46304632
extensions 1000 to max;
46314633
}
46324634

4635+
// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type.
4636+
message FieldMaskRules {
4637+
// `const` dictates that the field must match the specified value of the `google.protobuf.FieldMask` type exactly.
4638+
// If the field's value deviates from the specified value, an error message
4639+
// will be generated.
4640+
//
4641+
// ```proto
4642+
// message MyFieldMask {
4643+
// // value must equal ["a"]
4644+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask.const = {
4645+
// paths: ["a"]
4646+
// }];
4647+
// }
4648+
// ```
4649+
optional google.protobuf.FieldMask const = 1 [(predefined).cel = {
4650+
id: "field_mask.const"
4651+
expression: "this != getField(rules, 'const') ? 'value must equal %s'.format([getField(rules, 'const')]) : ''"
4652+
}];
4653+
4654+
// `in` requires the field value to only contain paths matching specified
4655+
// values or their subpaths.
4656+
// If any of the field value's paths doesn't match the rule,
4657+
// an error message is generated.
4658+
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
4659+
//
4660+
// ```proto
4661+
// message MyFieldMask {
4662+
// // The `value` FieldMask must only contain paths listed in `in`.
4663+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4664+
// in: ["a", "b", "c.a"]
4665+
// }];
4666+
// }
4667+
// ```
4668+
repeated string in = 2 [(predefined).cel = {
4669+
id: "field_mask.in"
4670+
expression: "!this.paths.all(p, p in getField(rules, 'in') || getField(rules, 'in').exists(f, p.startsWith(f+'.'))) ? 'value must only contain paths in %s'.format([getField(rules, 'in')]) : ''"
4671+
}];
4672+
4673+
// `not_in` requires the field value to not contain paths matching specified
4674+
// values or their subpaths.
4675+
// If any of the field value's paths matches the rule,
4676+
// an error message is generated.
4677+
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
4678+
//
4679+
// ```proto
4680+
// message MyFieldMask {
4681+
// // The `value` FieldMask shall not contain paths listed in `not_in`.
4682+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4683+
// not_in: ["forbidden", "immutable", "c.a"]
4684+
// }];
4685+
// }
4686+
// ```
4687+
repeated string not_in = 3 [(predefined).cel = {
4688+
id: "field_mask.not_in"
4689+
expression: "!this.paths.all(p, !(p in getField(rules, 'not_in') || getField(rules, 'not_in').exists(f, p.startsWith(f+'.')))) ? 'value must not contain any paths in %s'.format([getField(rules, 'not_in')]) : ''"
4690+
}];
4691+
4692+
// `example` specifies values that the field may have. These values SHOULD
4693+
// conform to other rules. `example` values will not impact validation
4694+
// but may be used as helpful guidance on how to populate the given field.
4695+
//
4696+
// ```proto
4697+
// message MyFieldMask {
4698+
// google.protobuf.FieldMask value = 1 [
4699+
// (buf.validate.field).field_mask.example = { paths: ["a", "b"] },
4700+
// (buf.validate.field).field_mask.example = { paths: ["c.a", "d"] },
4701+
// ];
4702+
// }
4703+
// ```
4704+
repeated google.protobuf.FieldMask example = 4 [(predefined).cel = {
4705+
id: "field_mask.example"
4706+
expression: "true"
4707+
}];
4708+
4709+
// Extension fields in this range that have the (buf.validate.predefined)
4710+
// option set will be treated as predefined field rules that can then be
4711+
// set on the field options of other fields to apply field rules.
4712+
// Extension numbers 1000 to 99999 are reserved for extension numbers that are
4713+
// defined in the [Protobuf Global Extension Registry][1]. Extension numbers
4714+
// above this range are reserved for extension numbers that are not explicitly
4715+
// assigned. For rules defined in publicly-consumed schemas, use of extensions
4716+
// above 99999 is discouraged due to the risk of conflicts.
4717+
//
4718+
// [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
4719+
extensions 1000 to max;
4720+
}
4721+
46334722
// TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type.
46344723
message TimestampRules {
46354724
// `const` dictates that this field, of the `google.protobuf.Timestamp` type, must exactly match the specified value. If the field value doesn't correspond to the specified timestamp, an error message will be generated.

0 commit comments

Comments
 (0)