Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ proto_library(
"strings.proto",
"wkt_any.proto",
"wkt_duration.proto",
"wkt_field_mask.proto",
"wkt_nested.proto",
"wkt_timestamp.proto",
"wkt_wrappers.proto",
Expand All @@ -56,6 +57,7 @@ proto_library(
"//proto/protovalidate/buf/validate:validate_proto",
"@com_google_protobuf//:any_proto",
"@com_google_protobuf//:duration_proto",
"@com_google_protobuf//:field_mask_proto",
"@com_google_protobuf//:timestamp_proto",
"@com_google_protobuf//:wrappers_proto",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2023-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package buf.validate.conformance.cases;

import "buf/validate/validate.proto";
import "google/protobuf/field_mask.proto";

message FieldMaskNone {
google.protobuf.FieldMask val = 1;
}
message FieldMaskRequired {
google.protobuf.FieldMask val = 1 [(buf.validate.field).required = true];
}

message FieldMaskConst {
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask.const = {
paths: ["a"]
}];
}

message FieldMaskIn {
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
in: [
"a",
"b"
]
}];
}
message FieldMaskNotIn {
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask = {
not_in: [
"c",
"d"
]
}];
}

message FieldMaskExample {
google.protobuf.FieldMask val = 1 [(buf.validate.field).field_mask.example = {
paths: ["a"]
}];
}
1 change: 1 addition & 0 deletions proto/protovalidate/buf/validate/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ proto_library(
deps = [
"@com_google_protobuf//:descriptor_proto",
"@com_google_protobuf//:duration_proto",
"@com_google_protobuf//:field_mask_proto",
"@com_google_protobuf//:timestamp_proto",
],
)
Expand Down
89 changes: 89 additions & 0 deletions proto/protovalidate/buf/validate/validate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package buf.validate;

import "google/protobuf/descriptor.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";

option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate";
Expand Down Expand Up @@ -313,6 +314,7 @@ message FieldRules {
// Well-Known Field Types
AnyRules any = 20;
DurationRules duration = 21;
FieldMaskRules field_mask = 28;
TimestampRules timestamp = 22;
}

Expand Down Expand Up @@ -4630,6 +4632,93 @@ message DurationRules {
extensions 1000 to max;
}

// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type.
message FieldMaskRules {
// `const` dictates that the field must match the specified value of the `google.protobuf.FieldMask` type exactly.
// If the field's value deviates from the specified value, an error message
// will be generated.
//
// ```proto
// message MyFieldMask {
// // value must equal ["a"]
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask.const = {
// paths: ["a"]
// }];
// }
// ```
optional google.protobuf.FieldMask const = 1 [(predefined).cel = {
id: "field_mask.const"
expression: "this != getField(rules, 'const') ? 'value must equal %s'.format([getField(rules, 'const')]) : ''"
}];

// `in` requires the field value to only contain paths matching specified
// values or their subpaths.
// If any of the field value's paths doesn't match the rule,
// an error message is generated.
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
//
// ```proto
// message MyFieldMask {
// // The `value` FieldMask must only contain paths listed in `in`.
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
// in: ["a", "b", "c.a"]
// }];
// }
// ```
repeated string in = 2 [(predefined).cel = {
id: "field_mask.in"
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')]) : ''"
}];

// `not_in` requires the field value to not contain paths matching specified
// values or their subpaths.
// If any of the field value's paths matches the rule,
// an error message is generated.
// See: https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
//
// ```proto
// message MyFieldMask {
// // The `value` FieldMask shall not contain paths listed in `not_in`.
// google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
// not_in: ["forbidden", "immutable", "c.a"]
// }];
// }
// ```
repeated string not_in = 3 [(predefined).cel = {
id: "field_mask.not_in"
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')]) : ''"
}];

// `example` specifies values that the field may have. These values SHOULD
// conform to other rules. `example` values will not impact validation
// but may be used as helpful guidance on how to populate the given field.
//
// ```proto
// message MyFieldMask {
// google.protobuf.FieldMask value = 1 [
// (buf.validate.field).field_mask.example = { paths: ["a", "b"] },
// (buf.validate.field).field_mask.example = { paths: ["c.a", "d"] },
// ];
// }
// ```
repeated google.protobuf.FieldMask example = 4 [(predefined).cel = {
id: "field_mask.example"
expression: "true"
}];

// Extension fields in this range that have the (buf.validate.predefined)
// option set will be treated as predefined field rules that can then be
// set on the field options of other fields to apply field rules.
// Extension numbers 1000 to 99999 are reserved for extension numbers that are
// defined in the [Protobuf Global Extension Registry][1]. Extension numbers
// above this range are reserved for extension numbers that are not explicitly
// assigned. For rules defined in publicly-consumed schemas, use of extensions
// above 99999 is discouraged due to the risk of conflicts.
//
// [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
extensions 1000 to max;
}

// TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type.
message TimestampRules {
// `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.
Expand Down
Loading
Loading