diff --git a/buf.gen.yaml b/buf.gen.yaml index 0e42127..915aee5 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -6,6 +6,7 @@ managed: except: - buf.build/envoyproxy/protoc-gen-validate - buf.build/bufbuild/protovalidate + - buf.build/googleapis/googleapis plugins: - plugin: buf.build/protocolbuffers/go:v1.31.0 out: internal/gen diff --git a/internal/evaluator/builder.go b/internal/evaluator/builder.go index 488bdf8..d0f8da4 100644 --- a/internal/evaluator/builder.go +++ b/internal/evaluator/builder.go @@ -268,7 +268,7 @@ func (bldr *Builder) processFieldExpressions( var opts []cel.EnvOption if fieldDesc.Kind() == protoreflect.MessageKind { opts = []cel.EnvOption{ - cel.TypeDescs(fieldDesc.Message().ParentFile()), + cel.Types(dynamicpb.NewMessage(fieldDesc.Message())), cel.Variable("this", cel.ObjectType(string(fieldDesc.Message().FullName()))), } } else { diff --git a/internal/gen/tests/example/v1/validations.pb.go b/internal/gen/tests/example/v1/validations.pb.go index fa42776..ed00da9 100644 --- a/internal/gen/tests/example/v1/validations.pb.go +++ b/internal/gen/tests/example/v1/validations.pb.go @@ -22,6 +22,7 @@ package examplev1 import ( _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + viewport "google.golang.org/genproto/googleapis/geo/type/viewport" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -508,6 +509,53 @@ func (x *TransitiveFieldConstraint) GetMask() *fieldmaskpb.FieldMask { return nil } +type MultipleStepsTransitiveFieldConstraints struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Viewport *viewport.Viewport `protobuf:"bytes,1,opt,name=viewport,proto3" json:"viewport,omitempty"` +} + +func (x *MultipleStepsTransitiveFieldConstraints) Reset() { + *x = MultipleStepsTransitiveFieldConstraints{} + if protoimpl.UnsafeEnabled { + mi := &file_tests_example_v1_validations_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MultipleStepsTransitiveFieldConstraints) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MultipleStepsTransitiveFieldConstraints) ProtoMessage() {} + +func (x *MultipleStepsTransitiveFieldConstraints) ProtoReflect() protoreflect.Message { + mi := &file_tests_example_v1_validations_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MultipleStepsTransitiveFieldConstraints.ProtoReflect.Descriptor instead. +func (*MultipleStepsTransitiveFieldConstraints) Descriptor() ([]byte, []int) { + return file_tests_example_v1_validations_proto_rawDescGZIP(), []int{8} +} + +func (x *MultipleStepsTransitiveFieldConstraints) GetViewport() *viewport.Viewport { + if x != nil { + return x.Viewport + } + return nil +} + var File_tests_example_v1_validations_proto protoreflect.FileDescriptor var file_tests_example_v1_validations_proto_rawDesc = []byte{ @@ -516,6 +564,8 @@ var file_tests_example_v1_validations_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6f, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x2f, 0x76, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8d, 0x02, 0x0a, 0x0b, 0x48, 0x61, 0x73, 0x4d, 0x73, 0x67, @@ -626,6 +676,20 @@ var file_tests_example_v1_validations_proto_rawDesc = []byte{ 0x6b, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x73, 0x29, 0x52, 0x04, 0x6d, 0x61, 0x73, 0x6b, + 0x22, 0xdd, 0x01, 0x0a, 0x27, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x65, + 0x70, 0x73, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0xb1, 0x01, 0x0a, + 0x08, 0x76, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x56, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x7a, 0xba, 0x48, 0x77, 0xba, + 0x01, 0x74, 0x0a, 0x15, 0x76, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x6f, 0x77, + 0x2e, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x41, 0x76, 0x69, 0x65, 0x77, 0x70, + 0x6f, 0x72, 0x74, 0x27, 0x73, 0x20, 0x6c, 0x6f, 0x77, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x27, + 0x73, 0x20, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x67, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, + 0x31, 0x30, 0x2e, 0x30, 0x20, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x73, 0x1a, 0x18, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x6c, 0x6f, 0x77, 0x2e, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x20, + 0x3e, 0x20, 0x31, 0x30, 0x2e, 0x30, 0x52, 0x08, 0x76, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x42, 0xd8, 0x01, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4c, 0x67, @@ -655,20 +719,22 @@ func file_tests_example_v1_validations_proto_rawDescGZIP() []byte { return file_tests_example_v1_validations_proto_rawDescData } -var file_tests_example_v1_validations_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_tests_example_v1_validations_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_tests_example_v1_validations_proto_goTypes = []interface{}{ - (*HasMsgExprs)(nil), // 0: tests.example.v1.HasMsgExprs - (*SelfRecursive)(nil), // 1: tests.example.v1.SelfRecursive - (*LoopRecursiveA)(nil), // 2: tests.example.v1.LoopRecursiveA - (*LoopRecursiveB)(nil), // 3: tests.example.v1.LoopRecursiveB - (*MsgHasOneof)(nil), // 4: tests.example.v1.MsgHasOneof - (*MsgHasRepeated)(nil), // 5: tests.example.v1.MsgHasRepeated - (*MsgHasMap)(nil), // 6: tests.example.v1.MsgHasMap - (*TransitiveFieldConstraint)(nil), // 7: tests.example.v1.TransitiveFieldConstraint - nil, // 8: tests.example.v1.MsgHasMap.Int32mapEntry - nil, // 9: tests.example.v1.MsgHasMap.StringMapEntry - nil, // 10: tests.example.v1.MsgHasMap.MessageMapEntry - (*fieldmaskpb.FieldMask)(nil), // 11: google.protobuf.FieldMask + (*HasMsgExprs)(nil), // 0: tests.example.v1.HasMsgExprs + (*SelfRecursive)(nil), // 1: tests.example.v1.SelfRecursive + (*LoopRecursiveA)(nil), // 2: tests.example.v1.LoopRecursiveA + (*LoopRecursiveB)(nil), // 3: tests.example.v1.LoopRecursiveB + (*MsgHasOneof)(nil), // 4: tests.example.v1.MsgHasOneof + (*MsgHasRepeated)(nil), // 5: tests.example.v1.MsgHasRepeated + (*MsgHasMap)(nil), // 6: tests.example.v1.MsgHasMap + (*TransitiveFieldConstraint)(nil), // 7: tests.example.v1.TransitiveFieldConstraint + (*MultipleStepsTransitiveFieldConstraints)(nil), // 8: tests.example.v1.MultipleStepsTransitiveFieldConstraints + nil, // 9: tests.example.v1.MsgHasMap.Int32mapEntry + nil, // 10: tests.example.v1.MsgHasMap.StringMapEntry + nil, // 11: tests.example.v1.MsgHasMap.MessageMapEntry + (*fieldmaskpb.FieldMask)(nil), // 12: google.protobuf.FieldMask + (*viewport.Viewport)(nil), // 13: google.geo.type.Viewport } var file_tests_example_v1_validations_proto_depIdxs = []int32{ 1, // 0: tests.example.v1.SelfRecursive.turtle:type_name -> tests.example.v1.SelfRecursive @@ -676,16 +742,17 @@ var file_tests_example_v1_validations_proto_depIdxs = []int32{ 2, // 2: tests.example.v1.LoopRecursiveB.a:type_name -> tests.example.v1.LoopRecursiveA 0, // 3: tests.example.v1.MsgHasOneof.msg:type_name -> tests.example.v1.HasMsgExprs 0, // 4: tests.example.v1.MsgHasRepeated.z:type_name -> tests.example.v1.HasMsgExprs - 8, // 5: tests.example.v1.MsgHasMap.int32map:type_name -> tests.example.v1.MsgHasMap.Int32mapEntry - 9, // 6: tests.example.v1.MsgHasMap.string_map:type_name -> tests.example.v1.MsgHasMap.StringMapEntry - 10, // 7: tests.example.v1.MsgHasMap.message_map:type_name -> tests.example.v1.MsgHasMap.MessageMapEntry - 11, // 8: tests.example.v1.TransitiveFieldConstraint.mask:type_name -> google.protobuf.FieldMask - 2, // 9: tests.example.v1.MsgHasMap.MessageMapEntry.value:type_name -> tests.example.v1.LoopRecursiveA - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 9, // 5: tests.example.v1.MsgHasMap.int32map:type_name -> tests.example.v1.MsgHasMap.Int32mapEntry + 10, // 6: tests.example.v1.MsgHasMap.string_map:type_name -> tests.example.v1.MsgHasMap.StringMapEntry + 11, // 7: tests.example.v1.MsgHasMap.message_map:type_name -> tests.example.v1.MsgHasMap.MessageMapEntry + 12, // 8: tests.example.v1.TransitiveFieldConstraint.mask:type_name -> google.protobuf.FieldMask + 13, // 9: tests.example.v1.MultipleStepsTransitiveFieldConstraints.viewport:type_name -> google.geo.type.Viewport + 2, // 10: tests.example.v1.MsgHasMap.MessageMapEntry.value:type_name -> tests.example.v1.LoopRecursiveA + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_tests_example_v1_validations_proto_init() } @@ -790,6 +857,18 @@ func file_tests_example_v1_validations_proto_init() { return nil } } + file_tests_example_v1_validations_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MultipleStepsTransitiveFieldConstraints); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_tests_example_v1_validations_proto_msgTypes[4].OneofWrappers = []interface{}{ (*MsgHasOneof_X)(nil), @@ -802,7 +881,7 @@ func file_tests_example_v1_validations_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tests_example_v1_validations_proto_rawDesc, NumEnums: 0, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/buf.lock b/proto/buf.lock index 88097ab..cdaff59 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -16,3 +16,8 @@ deps: repository: protoc-gen-validate commit: eac44469a7af47e7839a7f1f3d7ac004 digest: shake256:0feabcde01b6b11e3c75a5e3f807968d5995626546f39c37e5d4205892b3a59cced0ed83b35a2eb9e6dddd3309660ad46b737c9dcd224b425de0a6654ce04417 + - remote: buf.build + owner: googleapis + repository: googleapis + commit: 28151c0d0a1641bf938a7672c500e01d + digest: shake256:49215edf8ef57f7863004539deff8834cfb2195113f0b890dd1f67815d9353e28e668019165b9d872395871eeafcbab3ccfdb2b5f11734d3cca95be9e8d139de diff --git a/proto/buf.yaml b/proto/buf.yaml index bbcdd14..d9c965b 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -3,6 +3,7 @@ deps: - buf.build/bufbuild/protovalidate - buf.build/bufbuild/protovalidate-testing - buf.build/envoyproxy/protoc-gen-validate + - buf.build/googleapis/googleapis breaking: use: - FILE diff --git a/proto/tests/example/v1/validations.proto b/proto/tests/example/v1/validations.proto index e45b156..67ac4ea 100644 --- a/proto/tests/example/v1/validations.proto +++ b/proto/tests/example/v1/validations.proto @@ -17,6 +17,7 @@ syntax = "proto3"; package tests.example.v1; import "buf/validate/validate.proto"; +import "google/geo/type/viewport.proto"; import "google/protobuf/field_mask.proto"; message HasMsgExprs { @@ -131,3 +132,11 @@ message TransitiveFieldConstraint { expression: "has(this.paths)", }]; } + +message MultipleStepsTransitiveFieldConstraints { + google.geo.type.Viewport viewport = 1 [(buf.validate.field).cel = { + id: "viewport.low.latitude", + message: "viewport's low point's latitude must be greater than 10.0 degrees", + expression: "this.low.latitude > 10.0" + }]; +} diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..e385780 --- /dev/null +++ b/todo.txt @@ -0,0 +1,12 @@ +Tests: +- basic +- on repeated message field +- field message defined in this file +- field message defined in an imported file +- field message is wkt +- sub field rules has CEL +- the mesasge field has validation in its message definition +- the sub field has subfield has subfield, as in both this.foo.bar > 0 and foo.baz > 0 are defined +- top level defining two levels deep, equivalent to this.foo.bar.baz > 0 +- define sub field rules on a scalar type should fail + diff --git a/validator_test.go b/validator_test.go index ea7d985..e89927d 100644 --- a/validator_test.go +++ b/validator_test.go @@ -20,6 +20,8 @@ import ( pb "github.com/bufbuild/protovalidate-go/internal/gen/tests/example/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/genproto/googleapis/geo/type/viewport" + "google.golang.org/genproto/googleapis/type/latlng" "google.golang.org/protobuf/types/known/fieldmaskpb" ) @@ -135,3 +137,18 @@ func TestValidator_Validate_TransitiveFieldConstraints(t *testing.T) { err = val.Validate(msg) require.NoError(t, err) } + +func TestValidator_Validate_MultipleStepsTransitiveFieldConstraints(t *testing.T) { + t.Parallel() + val, err := New() + require.NoError(t, err) + msg := &pb.MultipleStepsTransitiveFieldConstraints{ + Viewport: &viewport.Viewport{ + Low: &latlng.LatLng{ + Latitude: 30.0, + }, + }, + } + err = val.Validate(msg) + require.NoError(t, err) +}