From ae8b0f9190290cac0e3875de60a8963f2ff0a3ac Mon Sep 17 00:00:00 2001 From: Christoph Hoopmann Date: Wed, 5 Nov 2025 14:19:13 +0100 Subject: [PATCH] Add FieldMaskRules supporting `const`, `in` and `not_in` This adds support for the well-known type google.protobuf.FieldMask: - const: exact matching of paths - in: accepted paths (violation if unlisted paths or subpaths are encountered) - not_in: rejected paths (violation if any specified path or subpath is encountered) Usage examples: const: ```proto message MyFieldMask { // The `value` FieldMask must be equal to `const`. google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask.const = { paths: ["foo", "bar"] }]; } ``` in: ```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"] }]; } ``` not_in: ```proto message MyFieldMask { // The `value` FieldMask must not contain paths listed in `not_in`. google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = { not_in: ["forbidden", "immutable", "c.a"] }]; } ``` Closes #428. Signed-off-by: Christoph Hoopmann --- .../validate/conformance/cases/BUILD.bazel | 2 + .../conformance/cases/wkt_field_mask.proto | 56 +++ proto/protovalidate/buf/validate/BUILD.bazel | 1 + .../protovalidate/buf/validate/validate.proto | 89 ++++ .../conformance/cases/wkt_field_mask.pb.go | 384 ++++++++++++++++++ .../internal/gen/buf/validate/validate.pb.go | 319 +++++++++++---- .../internal/cases/cases.go | 77 ++-- .../internal/cases/cases_field_mask.go | 125 ++++++ 8 files changed, 940 insertions(+), 113 deletions(-) create mode 100644 proto/protovalidate-testing/buf/validate/conformance/cases/wkt_field_mask.proto create mode 100644 tools/internal/gen/buf/validate/conformance/cases/wkt_field_mask.pb.go create mode 100644 tools/protovalidate-conformance/internal/cases/cases_field_mask.go diff --git a/proto/protovalidate-testing/buf/validate/conformance/cases/BUILD.bazel b/proto/protovalidate-testing/buf/validate/conformance/cases/BUILD.bazel index 5177c457..ca1be232 100644 --- a/proto/protovalidate-testing/buf/validate/conformance/cases/BUILD.bazel +++ b/proto/protovalidate-testing/buf/validate/conformance/cases/BUILD.bazel @@ -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", @@ -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", ], diff --git a/proto/protovalidate-testing/buf/validate/conformance/cases/wkt_field_mask.proto b/proto/protovalidate-testing/buf/validate/conformance/cases/wkt_field_mask.proto new file mode 100644 index 00000000..7748ea64 --- /dev/null +++ b/proto/protovalidate-testing/buf/validate/conformance/cases/wkt_field_mask.proto @@ -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"] + }]; +} diff --git a/proto/protovalidate/buf/validate/BUILD.bazel b/proto/protovalidate/buf/validate/BUILD.bazel index 867c39a6..19c4f796 100644 --- a/proto/protovalidate/buf/validate/BUILD.bazel +++ b/proto/protovalidate/buf/validate/BUILD.bazel @@ -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", ], ) diff --git a/proto/protovalidate/buf/validate/validate.proto b/proto/protovalidate/buf/validate/validate.proto index c095ad71..ea455942 100644 --- a/proto/protovalidate/buf/validate/validate.proto +++ b/proto/protovalidate/buf/validate/validate.proto @@ -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"; @@ -313,6 +314,7 @@ message FieldRules { // Well-Known Field Types AnyRules any = 20; DurationRules duration = 21; + FieldMaskRules field_mask = 28; TimestampRules timestamp = 22; } @@ -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. diff --git a/tools/internal/gen/buf/validate/conformance/cases/wkt_field_mask.pb.go b/tools/internal/gen/buf/validate/conformance/cases/wkt_field_mask.pb.go new file mode 100644 index 00000000..2e972cfc --- /dev/null +++ b/tools/internal/gen/buf/validate/conformance/cases/wkt_field_mask.pb.go @@ -0,0 +1,384 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc (unknown) +// source: buf/validate/conformance/cases/wkt_field_mask.proto + +package cases + +import ( + _ "github.com/bufbuild/protovalidate/tools/internal/gen/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type FieldMaskNone struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskNone) Reset() { + *x = FieldMaskNone{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskNone) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskNone) ProtoMessage() {} + +func (x *FieldMaskNone) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskNone.ProtoReflect.Descriptor instead. +func (*FieldMaskNone) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{0} +} + +func (x *FieldMaskNone) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +type FieldMaskRequired struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskRequired) Reset() { + *x = FieldMaskRequired{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskRequired) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskRequired) ProtoMessage() {} + +func (x *FieldMaskRequired) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskRequired.ProtoReflect.Descriptor instead. +func (*FieldMaskRequired) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{1} +} + +func (x *FieldMaskRequired) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +type FieldMaskConst struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskConst) Reset() { + *x = FieldMaskConst{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskConst) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskConst) ProtoMessage() {} + +func (x *FieldMaskConst) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskConst.ProtoReflect.Descriptor instead. +func (*FieldMaskConst) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{2} +} + +func (x *FieldMaskConst) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +type FieldMaskIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskIn) Reset() { + *x = FieldMaskIn{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskIn) ProtoMessage() {} + +func (x *FieldMaskIn) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskIn.ProtoReflect.Descriptor instead. +func (*FieldMaskIn) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{3} +} + +func (x *FieldMaskIn) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +type FieldMaskNotIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskNotIn) Reset() { + *x = FieldMaskNotIn{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskNotIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskNotIn) ProtoMessage() {} + +func (x *FieldMaskNotIn) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskNotIn.ProtoReflect.Descriptor instead. +func (*FieldMaskNotIn) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{4} +} + +func (x *FieldMaskNotIn) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +type FieldMaskExample struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskExample) Reset() { + *x = FieldMaskExample{} + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskExample) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskExample) ProtoMessage() {} + +func (x *FieldMaskExample) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskExample.ProtoReflect.Descriptor instead. +func (*FieldMaskExample) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP(), []int{5} +} + +func (x *FieldMaskExample) GetVal() *fieldmaskpb.FieldMask { + if x != nil { + return x.Val + } + return nil +} + +var File_buf_validate_conformance_cases_wkt_field_mask_proto protoreflect.FileDescriptor + +const file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDesc = "" + + "\n" + + "3buf/validate/conformance/cases/wkt_field_mask.proto\x12\x1ebuf.validate.conformance.cases\x1a\x1bbuf/validate/validate.proto\x1a google/protobuf/field_mask.proto\"=\n" + + "\rFieldMaskNone\x12,\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskR\x03val\"I\n" + + "\x11FieldMaskRequired\x124\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskB\x06\xbaH\x03\xc8\x01\x01R\x03val\"K\n" + + "\x0eFieldMaskConst\x129\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskB\v\xbaH\b\xe2\x01\x05\n" + + "\x03\n" + + "\x01aR\x03val\"I\n" + + "\vFieldMaskIn\x12:\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskB\f\xbaH\t\xe2\x01\x06\x12\x01a\x12\x01bR\x03val\"L\n" + + "\x0eFieldMaskNotIn\x12:\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskB\f\xbaH\t\xe2\x01\x06\x1a\x01c\x1a\x01dR\x03val\"M\n" + + "\x10FieldMaskExample\x129\n" + + "\x03val\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskB\v\xbaH\b\xe2\x01\x05\"\x03\n" + + "\x01aR\x03valB\xa8\x02\n" + + "\"com.buf.validate.conformance.casesB\x11WktFieldMaskProtoP\x01ZSgithub.com/bufbuild/protovalidate/tools/internal/gen/buf/validate/conformance/cases\xa2\x02\x04BVCC\xaa\x02\x1eBuf.Validate.Conformance.Cases\xca\x02\x1eBuf\\Validate\\Conformance\\Cases\xe2\x02*Buf\\Validate\\Conformance\\Cases\\GPBMetadata\xea\x02!Buf::Validate::Conformance::Casesb\x06proto3" + +var ( + file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescOnce sync.Once + file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescData []byte +) + +func file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescGZIP() []byte { + file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescOnce.Do(func() { + file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDesc), len(file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDesc))) + }) + return file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDescData +} + +var file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_buf_validate_conformance_cases_wkt_field_mask_proto_goTypes = []any{ + (*FieldMaskNone)(nil), // 0: buf.validate.conformance.cases.FieldMaskNone + (*FieldMaskRequired)(nil), // 1: buf.validate.conformance.cases.FieldMaskRequired + (*FieldMaskConst)(nil), // 2: buf.validate.conformance.cases.FieldMaskConst + (*FieldMaskIn)(nil), // 3: buf.validate.conformance.cases.FieldMaskIn + (*FieldMaskNotIn)(nil), // 4: buf.validate.conformance.cases.FieldMaskNotIn + (*FieldMaskExample)(nil), // 5: buf.validate.conformance.cases.FieldMaskExample + (*fieldmaskpb.FieldMask)(nil), // 6: google.protobuf.FieldMask +} +var file_buf_validate_conformance_cases_wkt_field_mask_proto_depIdxs = []int32{ + 6, // 0: buf.validate.conformance.cases.FieldMaskNone.val:type_name -> google.protobuf.FieldMask + 6, // 1: buf.validate.conformance.cases.FieldMaskRequired.val:type_name -> google.protobuf.FieldMask + 6, // 2: buf.validate.conformance.cases.FieldMaskConst.val:type_name -> google.protobuf.FieldMask + 6, // 3: buf.validate.conformance.cases.FieldMaskIn.val:type_name -> google.protobuf.FieldMask + 6, // 4: buf.validate.conformance.cases.FieldMaskNotIn.val:type_name -> google.protobuf.FieldMask + 6, // 5: buf.validate.conformance.cases.FieldMaskExample.val:type_name -> google.protobuf.FieldMask + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_buf_validate_conformance_cases_wkt_field_mask_proto_init() } +func file_buf_validate_conformance_cases_wkt_field_mask_proto_init() { + if File_buf_validate_conformance_cases_wkt_field_mask_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDesc), len(file_buf_validate_conformance_cases_wkt_field_mask_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_buf_validate_conformance_cases_wkt_field_mask_proto_goTypes, + DependencyIndexes: file_buf_validate_conformance_cases_wkt_field_mask_proto_depIdxs, + MessageInfos: file_buf_validate_conformance_cases_wkt_field_mask_proto_msgTypes, + }.Build() + File_buf_validate_conformance_cases_wkt_field_mask_proto = out.File + file_buf_validate_conformance_cases_wkt_field_mask_proto_goTypes = nil + file_buf_validate_conformance_cases_wkt_field_mask_proto_depIdxs = nil +} diff --git a/tools/internal/gen/buf/validate/validate.pb.go b/tools/internal/gen/buf/validate/validate.pb.go index e242fb4c..85ff043e 100644 --- a/tools/internal/gen/buf/validate/validate.pb.go +++ b/tools/internal/gen/buf/validate/validate.pb.go @@ -25,6 +25,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" descriptorpb "google.golang.org/protobuf/types/descriptorpb" durationpb "google.golang.org/protobuf/types/known/durationpb" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -675,6 +676,7 @@ type FieldRules struct { // *FieldRules_Map // *FieldRules_Any // *FieldRules_Duration + // *FieldRules_FieldMask // *FieldRules_Timestamp Type isFieldRules_Type `protobuf_oneof:"type"` unknownFields protoimpl.UnknownFields @@ -919,6 +921,15 @@ func (x *FieldRules) GetDuration() *DurationRules { return nil } +func (x *FieldRules) GetFieldMask() *FieldMaskRules { + if x != nil { + if x, ok := x.Type.(*FieldRules_FieldMask); ok { + return x.FieldMask + } + } + return nil +} + func (x *FieldRules) GetTimestamp() *TimestampRules { if x != nil { if x, ok := x.Type.(*FieldRules_Timestamp); ok { @@ -1015,6 +1026,10 @@ type FieldRules_Duration struct { Duration *DurationRules `protobuf:"bytes,21,opt,name=duration,oneof"` } +type FieldRules_FieldMask struct { + FieldMask *FieldMaskRules `protobuf:"bytes,28,opt,name=field_mask,json=fieldMask,oneof"` +} + type FieldRules_Timestamp struct { Timestamp *TimestampRules `protobuf:"bytes,22,opt,name=timestamp,oneof"` } @@ -1059,6 +1074,8 @@ func (*FieldRules_Any) isFieldRules_Type() {} func (*FieldRules_Duration) isFieldRules_Type() {} +func (*FieldRules_FieldMask) isFieldRules_Type() {} + func (*FieldRules_Timestamp) isFieldRules_Type() {} // PredefinedRules are custom rules that can be re-used with @@ -6647,6 +6664,136 @@ func (*DurationRules_Gt) isDurationRules_GreaterThan() {} func (*DurationRules_Gte) isDurationRules_GreaterThan() {} +// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type. +type FieldMaskRules struct { + state protoimpl.MessageState `protogen:"open.v1"` + // `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"] + // }]; + // } + // + // ``` + Const *fieldmaskpb.FieldMask `protobuf:"bytes,1,opt,name=const" json:"const,omitempty"` + // `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"] + // }]; + // } + // + // ``` + In []string `protobuf:"bytes,2,rep,name=in" json:"in,omitempty"` + // `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"] + // }]; + // } + // + // ``` + NotIn []string `protobuf:"bytes,3,rep,name=not_in,json=notIn" json:"not_in,omitempty"` + // `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"] }, + // ]; + // } + // + // ``` + Example []*fieldmaskpb.FieldMask `protobuf:"bytes,4,rep,name=example" json:"example,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldMaskRules) Reset() { + *x = FieldMaskRules{} + mi := &file_buf_validate_validate_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldMaskRules) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldMaskRules) ProtoMessage() {} + +func (x *FieldMaskRules) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_validate_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldMaskRules.ProtoReflect.Descriptor instead. +func (*FieldMaskRules) Descriptor() ([]byte, []int) { + return file_buf_validate_validate_proto_rawDescGZIP(), []int{26} +} + +func (x *FieldMaskRules) GetConst() *fieldmaskpb.FieldMask { + if x != nil { + return x.Const + } + return nil +} + +func (x *FieldMaskRules) GetIn() []string { + if x != nil { + return x.In + } + return nil +} + +func (x *FieldMaskRules) GetNotIn() []string { + if x != nil { + return x.NotIn + } + return nil +} + +func (x *FieldMaskRules) GetExample() []*fieldmaskpb.FieldMask { + if x != nil { + return x.Example + } + return nil +} + // TimestampRules describe the rules applied exclusively to the `google.protobuf.Timestamp` well-known type. type TimestampRules struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -6706,7 +6853,7 @@ type TimestampRules struct { func (x *TimestampRules) Reset() { *x = TimestampRules{} - mi := &file_buf_validate_validate_proto_msgTypes[26] + mi := &file_buf_validate_validate_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6718,7 +6865,7 @@ func (x *TimestampRules) String() string { func (*TimestampRules) ProtoMessage() {} func (x *TimestampRules) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_validate_proto_msgTypes[26] + mi := &file_buf_validate_validate_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6731,7 +6878,7 @@ func (x *TimestampRules) ProtoReflect() protoreflect.Message { // Deprecated: Use TimestampRules.ProtoReflect.Descriptor instead. func (*TimestampRules) Descriptor() ([]byte, []int) { - return file_buf_validate_validate_proto_rawDescGZIP(), []int{26} + return file_buf_validate_validate_proto_rawDescGZIP(), []int{27} } func (x *TimestampRules) GetConst() *timestamppb.Timestamp { @@ -6960,7 +7107,7 @@ type Violations struct { func (x *Violations) Reset() { *x = Violations{} - mi := &file_buf_validate_validate_proto_msgTypes[27] + mi := &file_buf_validate_validate_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6972,7 +7119,7 @@ func (x *Violations) String() string { func (*Violations) ProtoMessage() {} func (x *Violations) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_validate_proto_msgTypes[27] + mi := &file_buf_validate_validate_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6985,7 +7132,7 @@ func (x *Violations) ProtoReflect() protoreflect.Message { // Deprecated: Use Violations.ProtoReflect.Descriptor instead. func (*Violations) Descriptor() ([]byte, []int) { - return file_buf_validate_validate_proto_rawDescGZIP(), []int{27} + return file_buf_validate_validate_proto_rawDescGZIP(), []int{28} } func (x *Violations) GetViolations() []*Violation { @@ -7116,7 +7263,7 @@ type Violation struct { func (x *Violation) Reset() { *x = Violation{} - mi := &file_buf_validate_validate_proto_msgTypes[28] + mi := &file_buf_validate_validate_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7128,7 +7275,7 @@ func (x *Violation) String() string { func (*Violation) ProtoMessage() {} func (x *Violation) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_validate_proto_msgTypes[28] + mi := &file_buf_validate_validate_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7141,7 +7288,7 @@ func (x *Violation) ProtoReflect() protoreflect.Message { // Deprecated: Use Violation.ProtoReflect.Descriptor instead. func (*Violation) Descriptor() ([]byte, []int) { - return file_buf_validate_validate_proto_rawDescGZIP(), []int{28} + return file_buf_validate_validate_proto_rawDescGZIP(), []int{29} } func (x *Violation) GetField() *FieldPath { @@ -7193,7 +7340,7 @@ type FieldPath struct { func (x *FieldPath) Reset() { *x = FieldPath{} - mi := &file_buf_validate_validate_proto_msgTypes[29] + mi := &file_buf_validate_validate_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7205,7 +7352,7 @@ func (x *FieldPath) String() string { func (*FieldPath) ProtoMessage() {} func (x *FieldPath) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_validate_proto_msgTypes[29] + mi := &file_buf_validate_validate_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7218,7 +7365,7 @@ func (x *FieldPath) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldPath.ProtoReflect.Descriptor instead. func (*FieldPath) Descriptor() ([]byte, []int) { - return file_buf_validate_validate_proto_rawDescGZIP(), []int{29} + return file_buf_validate_validate_proto_rawDescGZIP(), []int{30} } func (x *FieldPath) GetElements() []*FieldPathElement { @@ -7274,7 +7421,7 @@ type FieldPathElement struct { func (x *FieldPathElement) Reset() { *x = FieldPathElement{} - mi := &file_buf_validate_validate_proto_msgTypes[30] + mi := &file_buf_validate_validate_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7286,7 +7433,7 @@ func (x *FieldPathElement) String() string { func (*FieldPathElement) ProtoMessage() {} func (x *FieldPathElement) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_validate_proto_msgTypes[30] + mi := &file_buf_validate_validate_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7299,7 +7446,7 @@ func (x *FieldPathElement) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldPathElement.ProtoReflect.Descriptor instead. func (*FieldPathElement) Descriptor() ([]byte, []int) { - return file_buf_validate_validate_proto_rawDescGZIP(), []int{30} + return file_buf_validate_validate_proto_rawDescGZIP(), []int{31} } func (x *FieldPathElement) GetFieldNumber() int32 { @@ -7515,7 +7662,7 @@ var File_buf_validate_validate_proto protoreflect.FileDescriptor const file_buf_validate_validate_proto_rawDesc = "" + "\n" + - "\x1bbuf/validate/validate.proto\x12\fbuf.validate\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"P\n" + + "\x1bbuf/validate/validate.proto\x12\fbuf.validate\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"P\n" + "\x04Rule\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x1e\n" + @@ -7530,7 +7677,8 @@ const file_buf_validate_validate_proto_rawDesc = "" + "\brequired\x18\x02 \x01(\bR\brequired\"(\n" + "\n" + "OneofRules\x12\x1a\n" + - "\brequired\x18\x01 \x01(\bR\brequired\"\xfd\t\n" + + "\brequired\x18\x01 \x01(\bR\brequired\"\xbc\n" + + "\n" + "\n" + "FieldRules\x12$\n" + "\x03cel\x18\x17 \x03(\v2\x12.buf.validate.RuleR\x03cel\x12\x1a\n" + @@ -7556,7 +7704,9 @@ const file_buf_validate_validate_proto_rawDesc = "" + "\brepeated\x18\x12 \x01(\v2\x1b.buf.validate.RepeatedRulesH\x00R\brepeated\x12*\n" + "\x03map\x18\x13 \x01(\v2\x16.buf.validate.MapRulesH\x00R\x03map\x12*\n" + "\x03any\x18\x14 \x01(\v2\x16.buf.validate.AnyRulesH\x00R\x03any\x129\n" + - "\bduration\x18\x15 \x01(\v2\x1b.buf.validate.DurationRulesH\x00R\bduration\x12<\n" + + "\bduration\x18\x15 \x01(\v2\x1b.buf.validate.DurationRulesH\x00R\bduration\x12=\n" + + "\n" + + "field_mask\x18\x1c \x01(\v2\x1c.buf.validate.FieldMaskRulesH\x00R\tfieldMask\x12<\n" + "\ttimestamp\x18\x16 \x01(\v2\x1c.buf.validate.TimestampRulesH\x00R\ttimestampB\x06\n" + "\x04typeJ\x04\b\x18\x10\x19J\x04\b\x1a\x10\x1bR\askippedR\fignore_empty\"Z\n" + "\x0fPredefinedRules\x12$\n" + @@ -8395,7 +8545,20 @@ const file_buf_validate_validate_proto_rawDesc = "" + "\x18\n" + "\x10duration.example\x1a\x04trueR\aexample*\t\b\xe8\a\x10\x80\x80\x80\x80\x02B\v\n" + "\tless_thanB\x0e\n" + - "\fgreater_than\"\xca\x18\n" + + "\fgreater_than\"\xfd\x05\n" + + "\x0eFieldMaskRules\x12\xab\x01\n" + + "\x05const\x18\x01 \x01(\v2\x1a.google.protobuf.FieldMaskBy\xc2Hv\n" + + "t\n" + + "\x10field_mask.const\x1a`this != getField(rules, 'const') ? 'value must equal %s'.format([getField(rules, 'const')]) : ''R\x05const\x12\xdd\x01\n" + + "\x02in\x18\x02 \x03(\tB\xcc\x01\xc2H\xc8\x01\n" + + "\xc5\x01\n" + + "\rfield_mask.in\x1a\xb3\x01!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')]) : ''R\x02in\x12\xfa\x01\n" + + "\x06not_in\x18\x03 \x03(\tB\xe2\x01\xc2H\xde\x01\n" + + "\xdb\x01\n" + + "\x11field_mask.not_in\x1a\xc5\x01!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')]) : ''R\x05notIn\x12U\n" + + "\aexample\x18\x04 \x03(\v2\x1a.google.protobuf.FieldMaskB\x1f\xc2H\x1c\n" + + "\x1a\n" + + "\x12field_mask.example\x1a\x04trueR\aexample*\t\b\xe8\a\x10\x80\x80\x80\x80\x02\"\xca\x18\n" + "\x0eTimestampRules\x12\xaa\x01\n" + "\x05const\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampBx\xc2Hu\n" + "s\n" + @@ -8504,7 +8667,7 @@ func file_buf_validate_validate_proto_rawDescGZIP() []byte { } var file_buf_validate_validate_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_buf_validate_validate_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_buf_validate_validate_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_buf_validate_validate_proto_goTypes = []any{ (Ignore)(0), // 0: buf.validate.Ignore (KnownRegex)(0), // 1: buf.validate.KnownRegex @@ -8534,17 +8697,19 @@ var file_buf_validate_validate_proto_goTypes = []any{ (*MapRules)(nil), // 25: buf.validate.MapRules (*AnyRules)(nil), // 26: buf.validate.AnyRules (*DurationRules)(nil), // 27: buf.validate.DurationRules - (*TimestampRules)(nil), // 28: buf.validate.TimestampRules - (*Violations)(nil), // 29: buf.validate.Violations - (*Violation)(nil), // 30: buf.validate.Violation - (*FieldPath)(nil), // 31: buf.validate.FieldPath - (*FieldPathElement)(nil), // 32: buf.validate.FieldPathElement - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 34: google.protobuf.Timestamp - (descriptorpb.FieldDescriptorProto_Type)(0), // 35: google.protobuf.FieldDescriptorProto.Type - (*descriptorpb.MessageOptions)(nil), // 36: google.protobuf.MessageOptions - (*descriptorpb.OneofOptions)(nil), // 37: google.protobuf.OneofOptions - (*descriptorpb.FieldOptions)(nil), // 38: google.protobuf.FieldOptions + (*FieldMaskRules)(nil), // 28: buf.validate.FieldMaskRules + (*TimestampRules)(nil), // 29: buf.validate.TimestampRules + (*Violations)(nil), // 30: buf.validate.Violations + (*Violation)(nil), // 31: buf.validate.Violation + (*FieldPath)(nil), // 32: buf.validate.FieldPath + (*FieldPathElement)(nil), // 33: buf.validate.FieldPathElement + (*durationpb.Duration)(nil), // 34: google.protobuf.Duration + (*fieldmaskpb.FieldMask)(nil), // 35: google.protobuf.FieldMask + (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp + (descriptorpb.FieldDescriptorProto_Type)(0), // 37: google.protobuf.FieldDescriptorProto.Type + (*descriptorpb.MessageOptions)(nil), // 38: google.protobuf.MessageOptions + (*descriptorpb.OneofOptions)(nil), // 39: google.protobuf.OneofOptions + (*descriptorpb.FieldOptions)(nil), // 40: google.protobuf.FieldOptions } var file_buf_validate_validate_proto_depIdxs = []int32{ 2, // 0: buf.validate.MessageRules.cel:type_name -> buf.validate.Rule @@ -8571,47 +8736,50 @@ var file_buf_validate_validate_proto_depIdxs = []int32{ 25, // 21: buf.validate.FieldRules.map:type_name -> buf.validate.MapRules 26, // 22: buf.validate.FieldRules.any:type_name -> buf.validate.AnyRules 27, // 23: buf.validate.FieldRules.duration:type_name -> buf.validate.DurationRules - 28, // 24: buf.validate.FieldRules.timestamp:type_name -> buf.validate.TimestampRules - 2, // 25: buf.validate.PredefinedRules.cel:type_name -> buf.validate.Rule - 1, // 26: buf.validate.StringRules.well_known_regex:type_name -> buf.validate.KnownRegex - 6, // 27: buf.validate.RepeatedRules.items:type_name -> buf.validate.FieldRules - 6, // 28: buf.validate.MapRules.keys:type_name -> buf.validate.FieldRules - 6, // 29: buf.validate.MapRules.values:type_name -> buf.validate.FieldRules - 33, // 30: buf.validate.DurationRules.const:type_name -> google.protobuf.Duration - 33, // 31: buf.validate.DurationRules.lt:type_name -> google.protobuf.Duration - 33, // 32: buf.validate.DurationRules.lte:type_name -> google.protobuf.Duration - 33, // 33: buf.validate.DurationRules.gt:type_name -> google.protobuf.Duration - 33, // 34: buf.validate.DurationRules.gte:type_name -> google.protobuf.Duration - 33, // 35: buf.validate.DurationRules.in:type_name -> google.protobuf.Duration - 33, // 36: buf.validate.DurationRules.not_in:type_name -> google.protobuf.Duration - 33, // 37: buf.validate.DurationRules.example:type_name -> google.protobuf.Duration - 34, // 38: buf.validate.TimestampRules.const:type_name -> google.protobuf.Timestamp - 34, // 39: buf.validate.TimestampRules.lt:type_name -> google.protobuf.Timestamp - 34, // 40: buf.validate.TimestampRules.lte:type_name -> google.protobuf.Timestamp - 34, // 41: buf.validate.TimestampRules.gt:type_name -> google.protobuf.Timestamp - 34, // 42: buf.validate.TimestampRules.gte:type_name -> google.protobuf.Timestamp - 33, // 43: buf.validate.TimestampRules.within:type_name -> google.protobuf.Duration - 34, // 44: buf.validate.TimestampRules.example:type_name -> google.protobuf.Timestamp - 30, // 45: buf.validate.Violations.violations:type_name -> buf.validate.Violation - 31, // 46: buf.validate.Violation.field:type_name -> buf.validate.FieldPath - 31, // 47: buf.validate.Violation.rule:type_name -> buf.validate.FieldPath - 32, // 48: buf.validate.FieldPath.elements:type_name -> buf.validate.FieldPathElement - 35, // 49: buf.validate.FieldPathElement.field_type:type_name -> google.protobuf.FieldDescriptorProto.Type - 35, // 50: buf.validate.FieldPathElement.key_type:type_name -> google.protobuf.FieldDescriptorProto.Type - 35, // 51: buf.validate.FieldPathElement.value_type:type_name -> google.protobuf.FieldDescriptorProto.Type - 36, // 52: buf.validate.message:extendee -> google.protobuf.MessageOptions - 37, // 53: buf.validate.oneof:extendee -> google.protobuf.OneofOptions - 38, // 54: buf.validate.field:extendee -> google.protobuf.FieldOptions - 38, // 55: buf.validate.predefined:extendee -> google.protobuf.FieldOptions - 3, // 56: buf.validate.message:type_name -> buf.validate.MessageRules - 5, // 57: buf.validate.oneof:type_name -> buf.validate.OneofRules - 6, // 58: buf.validate.field:type_name -> buf.validate.FieldRules - 7, // 59: buf.validate.predefined:type_name -> buf.validate.PredefinedRules - 60, // [60:60] is the sub-list for method output_type - 60, // [60:60] is the sub-list for method input_type - 56, // [56:60] is the sub-list for extension type_name - 52, // [52:56] is the sub-list for extension extendee - 0, // [0:52] is the sub-list for field type_name + 28, // 24: buf.validate.FieldRules.field_mask:type_name -> buf.validate.FieldMaskRules + 29, // 25: buf.validate.FieldRules.timestamp:type_name -> buf.validate.TimestampRules + 2, // 26: buf.validate.PredefinedRules.cel:type_name -> buf.validate.Rule + 1, // 27: buf.validate.StringRules.well_known_regex:type_name -> buf.validate.KnownRegex + 6, // 28: buf.validate.RepeatedRules.items:type_name -> buf.validate.FieldRules + 6, // 29: buf.validate.MapRules.keys:type_name -> buf.validate.FieldRules + 6, // 30: buf.validate.MapRules.values:type_name -> buf.validate.FieldRules + 34, // 31: buf.validate.DurationRules.const:type_name -> google.protobuf.Duration + 34, // 32: buf.validate.DurationRules.lt:type_name -> google.protobuf.Duration + 34, // 33: buf.validate.DurationRules.lte:type_name -> google.protobuf.Duration + 34, // 34: buf.validate.DurationRules.gt:type_name -> google.protobuf.Duration + 34, // 35: buf.validate.DurationRules.gte:type_name -> google.protobuf.Duration + 34, // 36: buf.validate.DurationRules.in:type_name -> google.protobuf.Duration + 34, // 37: buf.validate.DurationRules.not_in:type_name -> google.protobuf.Duration + 34, // 38: buf.validate.DurationRules.example:type_name -> google.protobuf.Duration + 35, // 39: buf.validate.FieldMaskRules.const:type_name -> google.protobuf.FieldMask + 35, // 40: buf.validate.FieldMaskRules.example:type_name -> google.protobuf.FieldMask + 36, // 41: buf.validate.TimestampRules.const:type_name -> google.protobuf.Timestamp + 36, // 42: buf.validate.TimestampRules.lt:type_name -> google.protobuf.Timestamp + 36, // 43: buf.validate.TimestampRules.lte:type_name -> google.protobuf.Timestamp + 36, // 44: buf.validate.TimestampRules.gt:type_name -> google.protobuf.Timestamp + 36, // 45: buf.validate.TimestampRules.gte:type_name -> google.protobuf.Timestamp + 34, // 46: buf.validate.TimestampRules.within:type_name -> google.protobuf.Duration + 36, // 47: buf.validate.TimestampRules.example:type_name -> google.protobuf.Timestamp + 31, // 48: buf.validate.Violations.violations:type_name -> buf.validate.Violation + 32, // 49: buf.validate.Violation.field:type_name -> buf.validate.FieldPath + 32, // 50: buf.validate.Violation.rule:type_name -> buf.validate.FieldPath + 33, // 51: buf.validate.FieldPath.elements:type_name -> buf.validate.FieldPathElement + 37, // 52: buf.validate.FieldPathElement.field_type:type_name -> google.protobuf.FieldDescriptorProto.Type + 37, // 53: buf.validate.FieldPathElement.key_type:type_name -> google.protobuf.FieldDescriptorProto.Type + 37, // 54: buf.validate.FieldPathElement.value_type:type_name -> google.protobuf.FieldDescriptorProto.Type + 38, // 55: buf.validate.message:extendee -> google.protobuf.MessageOptions + 39, // 56: buf.validate.oneof:extendee -> google.protobuf.OneofOptions + 40, // 57: buf.validate.field:extendee -> google.protobuf.FieldOptions + 40, // 58: buf.validate.predefined:extendee -> google.protobuf.FieldOptions + 3, // 59: buf.validate.message:type_name -> buf.validate.MessageRules + 5, // 60: buf.validate.oneof:type_name -> buf.validate.OneofRules + 6, // 61: buf.validate.field:type_name -> buf.validate.FieldRules + 7, // 62: buf.validate.predefined:type_name -> buf.validate.PredefinedRules + 63, // [63:63] is the sub-list for method output_type + 63, // [63:63] is the sub-list for method input_type + 59, // [59:63] is the sub-list for extension type_name + 55, // [55:59] is the sub-list for extension extendee + 0, // [0:55] is the sub-list for field type_name } func init() { file_buf_validate_validate_proto_init() } @@ -8640,6 +8808,7 @@ func file_buf_validate_validate_proto_init() { (*FieldRules_Map)(nil), (*FieldRules_Any)(nil), (*FieldRules_Duration)(nil), + (*FieldRules_FieldMask)(nil), (*FieldRules_Timestamp)(nil), } file_buf_validate_validate_proto_msgTypes[6].OneofWrappers = []any{ @@ -8746,7 +8915,7 @@ func file_buf_validate_validate_proto_init() { (*DurationRules_Gt)(nil), (*DurationRules_Gte)(nil), } - file_buf_validate_validate_proto_msgTypes[26].OneofWrappers = []any{ + file_buf_validate_validate_proto_msgTypes[27].OneofWrappers = []any{ (*TimestampRules_Lt)(nil), (*TimestampRules_Lte)(nil), (*TimestampRules_LtNow)(nil), @@ -8754,7 +8923,7 @@ func file_buf_validate_validate_proto_init() { (*TimestampRules_Gte)(nil), (*TimestampRules_GtNow)(nil), } - file_buf_validate_validate_proto_msgTypes[30].OneofWrappers = []any{ + file_buf_validate_validate_proto_msgTypes[31].OneofWrappers = []any{ (*FieldPathElement_Index)(nil), (*FieldPathElement_BoolKey)(nil), (*FieldPathElement_IntKey)(nil), @@ -8767,7 +8936,7 @@ func file_buf_validate_validate_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_buf_validate_validate_proto_rawDesc), len(file_buf_validate_validate_proto_rawDesc)), NumEnums: 2, - NumMessages: 31, + NumMessages: 32, NumExtensions: 4, NumServices: 0, }, diff --git a/tools/protovalidate-conformance/internal/cases/cases.go b/tools/protovalidate-conformance/internal/cases/cases.go index 99591796..ceb15366 100644 --- a/tools/protovalidate-conformance/internal/cases/cases.go +++ b/tools/protovalidate-conformance/internal/cases/cases.go @@ -23,43 +23,44 @@ import ( // tests can be found in /protos/testing/buf/validate/conformance/cases. func GlobalSuites() suites.Suites { return suites.Suites{ - "custom_rules": customSuite(), - "predefined_rules": predefinedSuite(), - "kitchen_sink": kitchenSinkSuite(), - "standard_rules/bool": boolSuite(), - "standard_rules/bytes": bytesSuite(), - "standard_rules/double": doubleSuite(), - "standard_rules/enum": enumSuite(), - "standard_rules/fixed32": fixed32Suite(), - "standard_rules/fixed64": fixed64Suite(), - "standard_rules/float": floatSuite(), - "standard_rules/int32": int32Suite(), - "standard_rules/int64": int64Suite(), - "standard_rules/map": mapSuite(), - "standard_rules/message": messageSuite(), - "standard_rules/nested": nestedSuite(), - "standard_rules/oneof": oneofSuite(), - "standard_rules/repeated": repeatedSuite(), - "standard_rules/required": requiredSuite(), - "standard_rules/ignore": ignoreSuite(), - "standard_rules/ignore_empty": ignoreEmptySuite(), - "standard_rules/sfixed32": sfixed32Suite(), - "standard_rules/sfixed64": sfixed64Suite(), - "standard_rules/sint32": sint32Suite(), - "standard_rules/sint64": sint64Suite(), - "standard_rules/string": stringSuite(), - "standard_rules/uint32": uint32Suite(), - "standard_rules/uint64": uint64Suite(), - "standard_rules/well_known_types/any": anySuite(), - "standard_rules/well_known_types/duration": durationSuite(), - "standard_rules/well_known_types/timestamp": timestampSuite(), - "standard_rules/well_known_types/wrapper": wrapperSuite(), - "library/is_hostname": isHostnameSuite(), - "library/is_host_and_port": isHostAndPortSuite(), - "library/is_ip_prefix": isIPPrefixSuite(), - "library/is_ip": isIPSuite(), - "library/is_email": isEmailSuite(), - "library/is_uri": isURISuite(), - "library/is_uri_ref": isURIRefSuite(), + "custom_rules": customSuite(), + "predefined_rules": predefinedSuite(), + "kitchen_sink": kitchenSinkSuite(), + "standard_rules/bool": boolSuite(), + "standard_rules/bytes": bytesSuite(), + "standard_rules/double": doubleSuite(), + "standard_rules/enum": enumSuite(), + "standard_rules/fixed32": fixed32Suite(), + "standard_rules/fixed64": fixed64Suite(), + "standard_rules/float": floatSuite(), + "standard_rules/int32": int32Suite(), + "standard_rules/int64": int64Suite(), + "standard_rules/map": mapSuite(), + "standard_rules/message": messageSuite(), + "standard_rules/nested": nestedSuite(), + "standard_rules/oneof": oneofSuite(), + "standard_rules/repeated": repeatedSuite(), + "standard_rules/required": requiredSuite(), + "standard_rules/ignore": ignoreSuite(), + "standard_rules/ignore_empty": ignoreEmptySuite(), + "standard_rules/sfixed32": sfixed32Suite(), + "standard_rules/sfixed64": sfixed64Suite(), + "standard_rules/sint32": sint32Suite(), + "standard_rules/sint64": sint64Suite(), + "standard_rules/string": stringSuite(), + "standard_rules/uint32": uint32Suite(), + "standard_rules/uint64": uint64Suite(), + "standard_rules/well_known_types/any": anySuite(), + "standard_rules/well_known_types/duration": durationSuite(), + "standard_rules/well_known_types/field_mask": fieldMaskSuite(), + "standard_rules/well_known_types/timestamp": timestampSuite(), + "standard_rules/well_known_types/wrapper": wrapperSuite(), + "library/is_hostname": isHostnameSuite(), + "library/is_host_and_port": isHostAndPortSuite(), + "library/is_ip_prefix": isIPPrefixSuite(), + "library/is_ip": isIPSuite(), + "library/is_email": isEmailSuite(), + "library/is_uri": isURISuite(), + "library/is_uri_ref": isURIRefSuite(), } } diff --git a/tools/protovalidate-conformance/internal/cases/cases_field_mask.go b/tools/protovalidate-conformance/internal/cases/cases_field_mask.go new file mode 100644 index 00000000..0b2f2a94 --- /dev/null +++ b/tools/protovalidate-conformance/internal/cases/cases_field_mask.go @@ -0,0 +1,125 @@ +// 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. + +package cases + +import ( + "github.com/bufbuild/protovalidate/tools/internal/gen/buf/validate" + "github.com/bufbuild/protovalidate/tools/internal/gen/buf/validate/conformance/cases" + "github.com/bufbuild/protovalidate/tools/protovalidate-conformance/internal/results" + "github.com/bufbuild/protovalidate/tools/protovalidate-conformance/internal/suites" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/fieldmaskpb" +) + +func fieldMaskSuite() suites.Suite { + return suites.Suite{ + "none/valid": { + Message: &cases.FieldMaskNone{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b"}}}, + Expected: results.Success(true), + }, + "required/valid": { + Message: &cases.FieldMaskRequired{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b"}}}, + Expected: results.Success(true), + }, + "required/invalid": { + Message: &cases.FieldMaskRequired{}, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("required"), + RuleId: proto.String("required"), + }), + }, + "const/valid": { + Message: &cases.FieldMaskConst{ + Val: &fieldmaskpb.FieldMask{ + Paths: []string{"a"}, + }, + }, + Expected: results.Success(true), + }, + "const/valid/empty": { + Message: &cases.FieldMaskConst{}, Expected: results.Success(true), + }, + "const/invalid": { + Message: &cases.FieldMaskConst{ + Val: &fieldmaskpb.FieldMask{ + Paths: []string{"b"}, + }, + }, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("field_mask.const"), + RuleId: proto.String("field_mask.const"), + Message: proto.String("value must equal a"), + }), + }, + "in/valid": { + Message: &cases.FieldMaskIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b"}}}, + Expected: results.Success(true), + }, + "in/valid/sub": { + Message: &cases.FieldMaskIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a.foo", "b.bar"}}}, + Expected: results.Success(true), + }, + "in/invalid": { + Message: &cases.FieldMaskIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b", "c"}}}, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("field_mask.in"), + RuleId: proto.String("field_mask.in"), + }), + }, + "in/invalid/sub": { + Message: &cases.FieldMaskIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b.foo", "c.bar"}}}, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("field_mask.in"), + RuleId: proto.String("field_mask.in"), + }), + }, + "not_in/valid": { + Message: &cases.FieldMaskNotIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b"}}}, + Expected: results.Success(true), + }, + "not_in/valid/sub": { + Message: &cases.FieldMaskNotIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a.foo", "b.bar"}}}, + Expected: results.Success(true), + }, + "not_in/invalid": { + Message: &cases.FieldMaskNotIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b", "c"}}}, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("field_mask.not_in"), + RuleId: proto.String("field_mask.not_in"), + }), + }, + "not_in/invalid/sub": { + Message: &cases.FieldMaskNotIn{Val: &fieldmaskpb.FieldMask{Paths: []string{"a", "b.foo", "c.bar"}}}, + Expected: results.Violations(&validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("field_mask.not_in"), + RuleId: proto.String("field_mask.not_in"), + }), + }, + "example/valid": { + Message: &cases.FieldMaskExample{ + Val: &fieldmaskpb.FieldMask{ + Paths: []string{"a"}, + }, + }, + Expected: results.Success(true), + }, + } +}