Skip to content

Commit b66b0b1

Browse files
committed
Add fieldmask rule to check for valid/invalid paths
Signed-off-by: Christoph Hoopmann <[email protected]>
1 parent 57a7e15 commit b66b0b1

File tree

6 files changed

+379
-112
lines changed

6 files changed

+379
-112
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 FieldMaskValid {
30+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
31+
valid: ["a", "b"]
32+
}];
33+
}
34+
message FieldMaskInvalid {
35+
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
36+
invalid: ["c", "d"]
37+
}];
38+
}

proto/protovalidate/buf/validate/validate.proto

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ message FieldRules {
313313
// Well-Known Field Types
314314
AnyRules any = 20;
315315
DurationRules duration = 21;
316+
FieldMaskRules field_mask = 28;
316317
TimestampRules timestamp = 22;
317318
}
318319

@@ -4605,6 +4606,53 @@ message DurationRules {
46054606
extensions 1000 to max;
46064607
}
46074608

4609+
// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type.
4610+
message FieldMaskRules {
4611+
// Requires the FieldMask paths to be a subset of `valid`.
4612+
// If any unknown path is included, an error message is generated.
4613+
//
4614+
// ```proto
4615+
// message MyFieldMask {
4616+
// // The `value` FieldMask must only contain paths listed in `valid`.
4617+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4618+
// valid: ["a", "b", "c.a"]
4619+
// }];
4620+
// }
4621+
// ```
4622+
repeated string valid = 1 [(predefined).cel = {
4623+
id: "field_mask.valid"
4624+
expression: "!this.paths.all(f, f in getField(rules, 'valid')) ? 'value must be a subset of %s'.format([getField(rules, 'valid')]) : ''"
4625+
}];
4626+
4627+
// Requires the FieldMask paths to not include any subset of `invalid`.
4628+
// If any invalid path is included, an error message is generated.
4629+
//
4630+
// ```proto
4631+
// message MyFieldMask {
4632+
// // The `value` FieldMask shall not contain paths listed in `invalid`.
4633+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4634+
// invalid: ["forbidden", "immutable", "c.a"]
4635+
// }];
4636+
// }
4637+
// ```
4638+
repeated string invalid = 2 [(predefined).cel = {
4639+
id: "field_mask.invalid"
4640+
expression: "!this.paths.all(f, !(f in getField(rules, 'invalid'))) ? 'value must not contain any subset of %s'.format([getField(rules, 'invalid')]) : ''"
4641+
}];
4642+
4643+
// Extension fields in this range that have the (buf.validate.predefined)
4644+
// option set will be treated as predefined field rules that can then be
4645+
// set on the field options of other fields to apply field rules.
4646+
// Extension numbers 1000 to 99999 are reserved for extension numbers that are
4647+
// defined in the [Protobuf Global Extension Registry][1]. Extension numbers
4648+
// above this range are reserved for extension numbers that are not explicitly
4649+
// assigned. For rules defined in publicly-consumed schemas, use of extensions
4650+
// above 99999 is discouraged due to the risk of conflicts.
4651+
//
4652+
// [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
4653+
extensions 1000 to max;
4654+
}
4655+
46084656
// TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type.
46094657
message TimestampRules {
46104658
// `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)