Skip to content

Commit 883ecdb

Browse files
committed
Add FieldMask rules
Signed-off-by: Sri Krishna <[email protected]>
1 parent 888a3a2 commit 883ecdb

File tree

4 files changed

+148
-7
lines changed

4 files changed

+148
-7
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Version of buf.build/bufbuild/protovalidate to use.
2-
protovalidate.version = 774f3764e09fcfc921b3ef5a42271754f0b7063a
2+
protovalidate.version = 8ed821b7c3ee9cad5d840a12ef32339af0dd2cbd
33

44
# Arguments to the protovalidate-conformance CLI
55
protovalidate.conformance.args = --strict_message --strict_error --expected_failures=expected-failures.yaml

src/main/java/build/buf/protovalidate/DescriptorMappings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ final class DescriptorMappings {
9292
EXPECTED_WKT_RULES.put("google.protobuf.Any", FIELD_RULES_DESC.findFieldByName("any"));
9393
EXPECTED_WKT_RULES.put(
9494
"google.protobuf.Duration", FIELD_RULES_DESC.findFieldByName("duration"));
95+
EXPECTED_WKT_RULES.put(
96+
"google.protobuf.FieldMask", FIELD_RULES_DESC.findFieldByName("field_mask"));
9597
EXPECTED_WKT_RULES.put(
9698
"google.protobuf.Timestamp", FIELD_RULES_DESC.findFieldByName("timestamp"));
9799
}

src/main/java/build/buf/protovalidate/EvaluatorBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ private static List<CompiledProgram> compileRules(
560560

561561
private static List<Rule> expressionsToRules(List<String> expressions) {
562562
return expressions.stream()
563-
.map(expr -> Rule.newBuilder().setExpression(expr).build())
563+
.map(expr -> Rule.newBuilder().setId(expr).setExpression(expr).build())
564564
.collect(Collectors.toList());
565565
}
566566
}

src/main/resources/buf/validate/validate.proto

Lines changed: 144 additions & 5 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";
@@ -113,7 +114,8 @@ message MessageRules {
113114
// rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax.
114115
//
115116
// This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for
116-
// simpler syntax when defining CEL Rules where `id` and `message` are largely redundant.
117+
// simpler syntax when defining CEL Rules where `id` and `message` derived from the `expression`. `id` will
118+
// be same as the `expression`.
117119
//
118120
// For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
119121
//
@@ -223,7 +225,8 @@ message FieldRules {
223225
// rule to be applied to this message. These rules are written in Common Expression Language (CEL) syntax.
224226
//
225227
// This is a simplified form of the `cel` Rule field, where only `expression` is set. This allows for
226-
// simpler syntax when defining CEL Rules where `id` and `message` are largely redundant.
228+
// simpler syntax when defining CEL Rules where `id` and `message` derived from the `expression`. `id` will
229+
// be same as the `expression`.
227230
//
228231
// For more information, [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
229232
//
@@ -233,7 +236,7 @@ message FieldRules {
233236
// optional int32 value = 1 [(buf.validate.field).cel_expression = "this > 42"];
234237
// }
235238
// ```
236-
repeated string cel_expression = 28;
239+
repeated string cel_expression = 29;
237240
// `cel` is a repeated field used to represent a textual expression
238241
// in the Common Expression Language (CEL) syntax. For more information,
239242
// [see our documentation](https://buf.build/docs/protovalidate/schemas/custom-rules/).
@@ -346,6 +349,7 @@ message FieldRules {
346349
// Well-Known Field Types
347350
AnyRules any = 20;
348351
DurationRules duration = 21;
352+
FieldMaskRules field_mask = 28;
349353
TimestampRules timestamp = 22;
350354
}
351355

@@ -3764,6 +3768,29 @@ message StringRules {
37643768
}
37653769
];
37663770

3771+
// `ulid` specifies that the field value must be a valid ULID (Universally Unique
3772+
// Lexicographically Sortable Identifier) as defined by the [ULID specification](https://github.com/ulid/spec).
3773+
// If the field value isn't a valid ULID, an error message will be generated.
3774+
//
3775+
// ```proto
3776+
// message MyString {
3777+
// // value must be a valid ULID
3778+
// string value = 1 [(buf.validate.field).string.ulid = true];
3779+
// }
3780+
// ```
3781+
bool ulid = 35 [
3782+
(predefined).cel = {
3783+
id: "string.ulid"
3784+
message: "value must be a valid ULID"
3785+
expression: "!rules.ulid || this == '' || this.matches('^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$')"
3786+
},
3787+
(predefined).cel = {
3788+
id: "string.ulid_empty"
3789+
message: "value is empty, which is not a valid ULID"
3790+
expression: "!rules.ulid || this != ''"
3791+
}
3792+
];
3793+
37673794
// `well_known_regex` specifies a common well-known pattern
37683795
// defined as a regex. If the field value doesn't match the well-known
37693796
// regex, an error message will be generated.
@@ -3976,7 +4003,7 @@ message BytesRules {
39764003
// the string.
39774004
// If the field value doesn't meet the requirement, an error message is generated.
39784005
//
3979-
// ```protobuf
4006+
// ```proto
39804007
// message MyBytes {
39814008
// // value does not contain \x02\x03
39824009
// optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"];
@@ -3991,7 +4018,7 @@ message BytesRules {
39914018
// values. If the field value doesn't match any of the specified values, an
39924019
// error message is generated.
39934020
//
3994-
// ```protobuf
4021+
// ```proto
39954022
// message MyBytes {
39964023
// // value must in ["\x01\x02", "\x02\x03", "\x03\x04"]
39974024
// optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}];
@@ -4085,6 +4112,31 @@ message BytesRules {
40854112
expression: "!rules.ipv6 || this.size() != 0"
40864113
}
40874114
];
4115+
4116+
// `uuid` ensures that the field `value` encodes the 128-bit UUID data as
4117+
// defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.2).
4118+
// The field must contain exactly 16 bytes
4119+
// representing the UUID. If the field value isn't a valid UUID, an error
4120+
// message will be generated.
4121+
//
4122+
// ```proto
4123+
// message MyBytes {
4124+
// // value must be a valid UUID
4125+
// optional bytes value = 1 [(buf.validate.field).bytes.uuid = true];
4126+
// }
4127+
// ```
4128+
bool uuid = 15 [
4129+
(predefined).cel = {
4130+
id: "bytes.uuid"
4131+
message: "value must be a valid UUID"
4132+
expression: "!rules.uuid || this.size() == 0 || this.size() == 16"
4133+
},
4134+
(predefined).cel = {
4135+
id: "bytes.uuid_empty"
4136+
message: "value is empty, which is not a valid UUID"
4137+
expression: "!rules.uuid || this.size() != 0"
4138+
}
4139+
];
40884140
}
40894141

40904142
// `example` specifies values that the field may have. These values SHOULD
@@ -4638,6 +4690,93 @@ message DurationRules {
46384690
extensions 1000 to max;
46394691
}
46404692

4693+
// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type.
4694+
message FieldMaskRules {
4695+
// `const` dictates that the field must match the specified value of the `google.protobuf.FieldMask` type exactly.
4696+
// If the field's value deviates from the specified value, an error message
4697+
// will be generated.
4698+
//
4699+
// ```proto
4700+
// message MyFieldMask {
4701+
// // value must equal ["a"]
4702+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask.const = {
4703+
// paths: ["a"]
4704+
// }];
4705+
// }
4706+
// ```
4707+
optional google.protobuf.FieldMask const = 1 [(predefined).cel = {
4708+
id: "field_mask.const"
4709+
expression: "this != getField(rules, 'const') ? 'value must equal paths %s'.format([getField(rules, 'const').paths]) : ''"
4710+
}];
4711+
4712+
// `in` requires the field value to only contain paths matching specified
4713+
// values or their subpaths.
4714+
// If any of the field value's paths doesn't match the rule,
4715+
// an error message is generated.
4716+
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
4717+
//
4718+
// ```proto
4719+
// message MyFieldMask {
4720+
// // The `value` FieldMask must only contain paths listed in `in`.
4721+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4722+
// in: ["a", "b", "c.a"]
4723+
// }];
4724+
// }
4725+
// ```
4726+
repeated string in = 2 [(predefined).cel = {
4727+
id: "field_mask.in"
4728+
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')]) : ''"
4729+
}];
4730+
4731+
// `not_in` requires the field value to not contain paths matching specified
4732+
// values or their subpaths.
4733+
// If any of the field value's paths matches the rule,
4734+
// an error message is generated.
4735+
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
4736+
//
4737+
// ```proto
4738+
// message MyFieldMask {
4739+
// // The `value` FieldMask shall not contain paths listed in `not_in`.
4740+
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
4741+
// not_in: ["forbidden", "immutable", "c.a"]
4742+
// }];
4743+
// }
4744+
// ```
4745+
repeated string not_in = 3 [(predefined).cel = {
4746+
id: "field_mask.not_in"
4747+
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')]) : ''"
4748+
}];
4749+
4750+
// `example` specifies values that the field may have. These values SHOULD
4751+
// conform to other rules. `example` values will not impact validation
4752+
// but may be used as helpful guidance on how to populate the given field.
4753+
//
4754+
// ```proto
4755+
// message MyFieldMask {
4756+
// google.protobuf.FieldMask value = 1 [
4757+
// (buf.validate.field).field_mask.example = { paths: ["a", "b"] },
4758+
// (buf.validate.field).field_mask.example = { paths: ["c.a", "d"] },
4759+
// ];
4760+
// }
4761+
// ```
4762+
repeated google.protobuf.FieldMask example = 4 [(predefined).cel = {
4763+
id: "field_mask.example"
4764+
expression: "true"
4765+
}];
4766+
4767+
// Extension fields in this range that have the (buf.validate.predefined)
4768+
// option set will be treated as predefined field rules that can then be
4769+
// set on the field options of other fields to apply field rules.
4770+
// Extension numbers 1000 to 99999 are reserved for extension numbers that are
4771+
// defined in the [Protobuf Global Extension Registry][1]. Extension numbers
4772+
// above this range are reserved for extension numbers that are not explicitly
4773+
// assigned. For rules defined in publicly-consumed schemas, use of extensions
4774+
// above 99999 is discouraged due to the risk of conflicts.
4775+
//
4776+
// [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
4777+
extensions 1000 to max;
4778+
}
4779+
46414780
// TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type.
46424781
message TimestampRules {
46434782
// `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)