diff --git a/Makefile b/Makefile index 9e41f9fc..4b7519d2 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ GOLANGCI_LINT_VERSION ?= v2.1.2 # Set to use a different version of protovalidate-conformance. # Should be kept in sync with the version referenced in buf.yaml and # 'buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go' in go.mod. -CONFORMANCE_VERSION ?= v1.0.0-rc.1 +CONFORMANCE_VERSION ?= v1.0.0-rc.2 .PHONY: help help: ## Describe useful make targets diff --git a/buf.yaml b/buf.yaml index ebf33f6c..3a053e45 100644 --- a/buf.yaml +++ b/buf.yaml @@ -2,8 +2,8 @@ version: v2 modules: - path: proto deps: - - buf.build/bufbuild/protovalidate:v1.0.0-rc.1 - - buf.build/bufbuild/protovalidate-testing:v1.0.0-rc.1 + - buf.build/bufbuild/protovalidate:v1.0.0-rc.2 + - buf.build/bufbuild/protovalidate-testing:v1.0.0-rc.2 lint: use: - STANDARD diff --git a/builder.go b/builder.go index b64b7363..d6d6a36a 100644 --- a/builder.go +++ b/builder.go @@ -127,6 +127,25 @@ func (bldr *builder) buildMessage( return } + oneofRules := msgRules.GetOneof() + for _, rule := range oneofRules { + fdescs := make([]protoreflect.FieldDescriptor, 0, len(rule.GetFields())) + for _, name := range rule.GetFields() { + fdesc := desc.Fields().ByName(protoreflect.Name(name)) + if fdesc == nil { + msgEval.Err = &CompilationError{cause: fmt.Errorf( + "field %q not found in message %s", name, desc.FullName())} + } else { + fdescs = append(fdescs, fdesc) + } + } + oneofEval := &oneofEvaluator{ + Fields: fdescs, + Required: rule.GetRequired(), + } + msgEval.AppendNested(oneofEval) + } + steps := []func( desc protoreflect.MessageDescriptor, msgRules *validate.MessageRules, diff --git a/go.mod b/go.mod index 0065f3fc..b9014d18 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module buf.build/go/protovalidate go 1.23.0 require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c7344d9f5dae.1 + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c6451e2c05a6.1 github.com/google/cel-go v0.25.0 github.com/stretchr/testify v1.10.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index 7715c250..bd199c8b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c7344d9f5dae.1 h1:nmsl+LeZt89FPjFhJvd0LTaUjmLy4MDAC+XEbzXc3xU= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c7344d9f5dae.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c6451e2c05a6.1 h1:foZfk56KWDz932pQ0gr0OfM5GrEBZVJvxyBzPBkik/U= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-00000000000000-c6451e2c05a6.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= diff --git a/internal/gen/buf/validate/conformance/cases/messages.pb.go b/internal/gen/buf/validate/conformance/cases/messages.pb.go index 71f8a6f6..096fe9ac 100644 --- a/internal/gen/buf/validate/conformance/cases/messages.pb.go +++ b/internal/gen/buf/validate/conformance/cases/messages.pb.go @@ -765,6 +765,276 @@ func (b0 MessageWith3DInside_builder) Build() *MessageWith3DInside { return m0 } +type MessageOneofSingleField struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3" json:"str_field,omitempty"` + BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3" json:"bool_field,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofSingleField) Reset() { + *x = MessageOneofSingleField{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofSingleField) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofSingleField) ProtoMessage() {} + +func (x *MessageOneofSingleField) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofSingleField) GetStrField() string { + if x != nil { + return x.StrField + } + return "" +} + +func (x *MessageOneofSingleField) GetBoolField() bool { + if x != nil { + return x.BoolField + } + return false +} + +func (x *MessageOneofSingleField) SetStrField(v string) { + x.StrField = v +} + +func (x *MessageOneofSingleField) SetBoolField(v bool) { + x.BoolField = v +} + +type MessageOneofSingleField_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofSingleField_builder) Build() *MessageOneofSingleField { + m0 := &MessageOneofSingleField{} + b, x := &b0, m0 + _, _ = b, x + x.StrField = b.StrField + x.BoolField = b.BoolField + return m0 +} + +type MessageOneofMultipleFields struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3" json:"str_field,omitempty"` + BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3" json:"bool_field,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofMultipleFields) Reset() { + *x = MessageOneofMultipleFields{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofMultipleFields) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofMultipleFields) ProtoMessage() {} + +func (x *MessageOneofMultipleFields) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofMultipleFields) GetStrField() string { + if x != nil { + return x.StrField + } + return "" +} + +func (x *MessageOneofMultipleFields) GetBoolField() bool { + if x != nil { + return x.BoolField + } + return false +} + +func (x *MessageOneofMultipleFields) SetStrField(v string) { + x.StrField = v +} + +func (x *MessageOneofMultipleFields) SetBoolField(v bool) { + x.BoolField = v +} + +type MessageOneofMultipleFields_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofMultipleFields_builder) Build() *MessageOneofMultipleFields { + m0 := &MessageOneofMultipleFields{} + b, x := &b0, m0 + _, _ = b, x + x.StrField = b.StrField + x.BoolField = b.BoolField + return m0 +} + +type MessageOneofMultipleFieldsRequired struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3" json:"str_field,omitempty"` + BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3" json:"bool_field,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofMultipleFieldsRequired) Reset() { + *x = MessageOneofMultipleFieldsRequired{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofMultipleFieldsRequired) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofMultipleFieldsRequired) ProtoMessage() {} + +func (x *MessageOneofMultipleFieldsRequired) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofMultipleFieldsRequired) GetStrField() string { + if x != nil { + return x.StrField + } + return "" +} + +func (x *MessageOneofMultipleFieldsRequired) GetBoolField() bool { + if x != nil { + return x.BoolField + } + return false +} + +func (x *MessageOneofMultipleFieldsRequired) SetStrField(v string) { + x.StrField = v +} + +func (x *MessageOneofMultipleFieldsRequired) SetBoolField(v bool) { + x.BoolField = v +} + +type MessageOneofMultipleFieldsRequired_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofMultipleFieldsRequired_builder) Build() *MessageOneofMultipleFieldsRequired { + m0 := &MessageOneofMultipleFieldsRequired{} + b, x := &b0, m0 + _, _ = b, x + x.StrField = b.StrField + x.BoolField = b.BoolField + return m0 +} + +type MessageOneofUnknownFieldName struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3" json:"str_field,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofUnknownFieldName) Reset() { + *x = MessageOneofUnknownFieldName{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofUnknownFieldName) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofUnknownFieldName) ProtoMessage() {} + +func (x *MessageOneofUnknownFieldName) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofUnknownFieldName) GetStrField() string { + if x != nil { + return x.StrField + } + return "" +} + +func (x *MessageOneofUnknownFieldName) SetStrField(v string) { + x.StrField = v +} + +type MessageOneofUnknownFieldName_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string +} + +func (b0 MessageOneofUnknownFieldName_builder) Build() *MessageOneofUnknownFieldName { + m0 := &MessageOneofUnknownFieldName{} + b, x := &b0, m0 + _, _ = b, x + x.StrField = b.StrField + return m0 +} + type MessageNone_NoneMsg struct { state protoimpl.MessageState `protogen:"hybrid.v1"` unknownFields protoimpl.UnknownFields @@ -773,7 +1043,7 @@ type MessageNone_NoneMsg struct { func (x *MessageNone_NoneMsg) Reset() { *x = MessageNone_NoneMsg{} - mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -785,7 +1055,7 @@ func (x *MessageNone_NoneMsg) String() string { func (*MessageNone_NoneMsg) ProtoMessage() {} func (x *MessageNone_NoneMsg) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -837,29 +1107,56 @@ const file_buf_validate_conformance_cases_messages_proto_rawDesc = "" + "\x14MessageRequiredOneof\x12C\n" + "\x03val\x18\x01 \x01(\v2'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03valB\f\n" + "\x03one\x12\x05\xbaH\x02\b\x01\"\x15\n" + - "\x13MessageWith3dInsideB\x97\x02\n" + + "\x13MessageWith3dInside\"g\n" + + "\x17MessageOneofSingleField\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x10\xbaH\r\"\v\n" + + "\tstr_field\"v\n" + + "\x1aMessageOneofMultipleFields\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x1c\xbaH\x19\"\x17\n" + + "\tstr_field\n" + + "\n" + + "bool_field\"\x80\x01\n" + + "\"MessageOneofMultipleFieldsRequired\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x1e\xbaH\x1b\"\x19\n" + + "\tstr_field\n" + + "\n" + + "bool_field\x10\x01\"G\n" + + "\x1cMessageOneofUnknownFieldName\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField:\n" + + "\xbaH\a\"\x05\n" + + "\x03xxxB\x97\x02\n" + "\"com.buf.validate.conformance.casesB\rMessagesProtoP\x01ZFbuf.build/go/protovalidate/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_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_buf_validate_conformance_cases_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_buf_validate_conformance_cases_messages_proto_goTypes = []any{ - (*TestMsg)(nil), // 0: buf.validate.conformance.cases.TestMsg - (*MessageNone)(nil), // 1: buf.validate.conformance.cases.MessageNone - (*MessageDisabled)(nil), // 2: buf.validate.conformance.cases.MessageDisabled - (*Message)(nil), // 3: buf.validate.conformance.cases.Message - (*MessageCrossPackage)(nil), // 4: buf.validate.conformance.cases.MessageCrossPackage - (*MessageSkip)(nil), // 5: buf.validate.conformance.cases.MessageSkip - (*MessageRequired)(nil), // 6: buf.validate.conformance.cases.MessageRequired - (*MessageRequiredButOptional)(nil), // 7: buf.validate.conformance.cases.MessageRequiredButOptional - (*MessageRequiredOneof)(nil), // 8: buf.validate.conformance.cases.MessageRequiredOneof - (*MessageWith3DInside)(nil), // 9: buf.validate.conformance.cases.MessageWith3dInside - (*MessageNone_NoneMsg)(nil), // 10: buf.validate.conformance.cases.MessageNone.NoneMsg - (*other_package.Embed)(nil), // 11: buf.validate.conformance.cases.other_package.Embed + (*TestMsg)(nil), // 0: buf.validate.conformance.cases.TestMsg + (*MessageNone)(nil), // 1: buf.validate.conformance.cases.MessageNone + (*MessageDisabled)(nil), // 2: buf.validate.conformance.cases.MessageDisabled + (*Message)(nil), // 3: buf.validate.conformance.cases.Message + (*MessageCrossPackage)(nil), // 4: buf.validate.conformance.cases.MessageCrossPackage + (*MessageSkip)(nil), // 5: buf.validate.conformance.cases.MessageSkip + (*MessageRequired)(nil), // 6: buf.validate.conformance.cases.MessageRequired + (*MessageRequiredButOptional)(nil), // 7: buf.validate.conformance.cases.MessageRequiredButOptional + (*MessageRequiredOneof)(nil), // 8: buf.validate.conformance.cases.MessageRequiredOneof + (*MessageWith3DInside)(nil), // 9: buf.validate.conformance.cases.MessageWith3dInside + (*MessageOneofSingleField)(nil), // 10: buf.validate.conformance.cases.MessageOneofSingleField + (*MessageOneofMultipleFields)(nil), // 11: buf.validate.conformance.cases.MessageOneofMultipleFields + (*MessageOneofMultipleFieldsRequired)(nil), // 12: buf.validate.conformance.cases.MessageOneofMultipleFieldsRequired + (*MessageOneofUnknownFieldName)(nil), // 13: buf.validate.conformance.cases.MessageOneofUnknownFieldName + (*MessageNone_NoneMsg)(nil), // 14: buf.validate.conformance.cases.MessageNone.NoneMsg + (*other_package.Embed)(nil), // 15: buf.validate.conformance.cases.other_package.Embed } var file_buf_validate_conformance_cases_messages_proto_depIdxs = []int32{ 0, // 0: buf.validate.conformance.cases.TestMsg.nested:type_name -> buf.validate.conformance.cases.TestMsg - 10, // 1: buf.validate.conformance.cases.MessageNone.val:type_name -> buf.validate.conformance.cases.MessageNone.NoneMsg + 14, // 1: buf.validate.conformance.cases.MessageNone.val:type_name -> buf.validate.conformance.cases.MessageNone.NoneMsg 0, // 2: buf.validate.conformance.cases.Message.val:type_name -> buf.validate.conformance.cases.TestMsg - 11, // 3: buf.validate.conformance.cases.MessageCrossPackage.val:type_name -> buf.validate.conformance.cases.other_package.Embed + 15, // 3: buf.validate.conformance.cases.MessageCrossPackage.val:type_name -> buf.validate.conformance.cases.other_package.Embed 0, // 4: buf.validate.conformance.cases.MessageSkip.val:type_name -> buf.validate.conformance.cases.TestMsg 0, // 5: buf.validate.conformance.cases.MessageRequired.val:type_name -> buf.validate.conformance.cases.TestMsg 0, // 6: buf.validate.conformance.cases.MessageRequiredButOptional.val:type_name -> buf.validate.conformance.cases.TestMsg @@ -886,7 +1183,7 @@ func file_buf_validate_conformance_cases_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_buf_validate_conformance_cases_messages_proto_rawDesc), len(file_buf_validate_conformance_cases_messages_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/gen/buf/validate/conformance/cases/messages_protoopaque.pb.go b/internal/gen/buf/validate/conformance/cases/messages_protoopaque.pb.go index e5e263cf..d1271742 100644 --- a/internal/gen/buf/validate/conformance/cases/messages_protoopaque.pb.go +++ b/internal/gen/buf/validate/conformance/cases/messages_protoopaque.pb.go @@ -755,6 +755,276 @@ func (b0 MessageWith3DInside_builder) Build() *MessageWith3DInside { return m0 } +type MessageOneofSingleField struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3"` + xxx_hidden_BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofSingleField) Reset() { + *x = MessageOneofSingleField{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofSingleField) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofSingleField) ProtoMessage() {} + +func (x *MessageOneofSingleField) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofSingleField) GetStrField() string { + if x != nil { + return x.xxx_hidden_StrField + } + return "" +} + +func (x *MessageOneofSingleField) GetBoolField() bool { + if x != nil { + return x.xxx_hidden_BoolField + } + return false +} + +func (x *MessageOneofSingleField) SetStrField(v string) { + x.xxx_hidden_StrField = v +} + +func (x *MessageOneofSingleField) SetBoolField(v bool) { + x.xxx_hidden_BoolField = v +} + +type MessageOneofSingleField_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofSingleField_builder) Build() *MessageOneofSingleField { + m0 := &MessageOneofSingleField{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_StrField = b.StrField + x.xxx_hidden_BoolField = b.BoolField + return m0 +} + +type MessageOneofMultipleFields struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3"` + xxx_hidden_BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofMultipleFields) Reset() { + *x = MessageOneofMultipleFields{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofMultipleFields) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofMultipleFields) ProtoMessage() {} + +func (x *MessageOneofMultipleFields) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofMultipleFields) GetStrField() string { + if x != nil { + return x.xxx_hidden_StrField + } + return "" +} + +func (x *MessageOneofMultipleFields) GetBoolField() bool { + if x != nil { + return x.xxx_hidden_BoolField + } + return false +} + +func (x *MessageOneofMultipleFields) SetStrField(v string) { + x.xxx_hidden_StrField = v +} + +func (x *MessageOneofMultipleFields) SetBoolField(v bool) { + x.xxx_hidden_BoolField = v +} + +type MessageOneofMultipleFields_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofMultipleFields_builder) Build() *MessageOneofMultipleFields { + m0 := &MessageOneofMultipleFields{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_StrField = b.StrField + x.xxx_hidden_BoolField = b.BoolField + return m0 +} + +type MessageOneofMultipleFieldsRequired struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3"` + xxx_hidden_BoolField bool `protobuf:"varint,2,opt,name=bool_field,json=boolField,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofMultipleFieldsRequired) Reset() { + *x = MessageOneofMultipleFieldsRequired{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofMultipleFieldsRequired) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofMultipleFieldsRequired) ProtoMessage() {} + +func (x *MessageOneofMultipleFieldsRequired) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofMultipleFieldsRequired) GetStrField() string { + if x != nil { + return x.xxx_hidden_StrField + } + return "" +} + +func (x *MessageOneofMultipleFieldsRequired) GetBoolField() bool { + if x != nil { + return x.xxx_hidden_BoolField + } + return false +} + +func (x *MessageOneofMultipleFieldsRequired) SetStrField(v string) { + x.xxx_hidden_StrField = v +} + +func (x *MessageOneofMultipleFieldsRequired) SetBoolField(v bool) { + x.xxx_hidden_BoolField = v +} + +type MessageOneofMultipleFieldsRequired_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string + BoolField bool +} + +func (b0 MessageOneofMultipleFieldsRequired_builder) Build() *MessageOneofMultipleFieldsRequired { + m0 := &MessageOneofMultipleFieldsRequired{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_StrField = b.StrField + x.xxx_hidden_BoolField = b.BoolField + return m0 +} + +type MessageOneofUnknownFieldName struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_StrField string `protobuf:"bytes,1,opt,name=str_field,json=strField,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MessageOneofUnknownFieldName) Reset() { + *x = MessageOneofUnknownFieldName{} + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MessageOneofUnknownFieldName) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageOneofUnknownFieldName) ProtoMessage() {} + +func (x *MessageOneofUnknownFieldName) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MessageOneofUnknownFieldName) GetStrField() string { + if x != nil { + return x.xxx_hidden_StrField + } + return "" +} + +func (x *MessageOneofUnknownFieldName) SetStrField(v string) { + x.xxx_hidden_StrField = v +} + +type MessageOneofUnknownFieldName_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + StrField string +} + +func (b0 MessageOneofUnknownFieldName_builder) Build() *MessageOneofUnknownFieldName { + m0 := &MessageOneofUnknownFieldName{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_StrField = b.StrField + return m0 +} + type MessageNone_NoneMsg struct { state protoimpl.MessageState `protogen:"opaque.v1"` unknownFields protoimpl.UnknownFields @@ -763,7 +1033,7 @@ type MessageNone_NoneMsg struct { func (x *MessageNone_NoneMsg) Reset() { *x = MessageNone_NoneMsg{} - mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -775,7 +1045,7 @@ func (x *MessageNone_NoneMsg) String() string { func (*MessageNone_NoneMsg) ProtoMessage() {} func (x *MessageNone_NoneMsg) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[10] + mi := &file_buf_validate_conformance_cases_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -827,29 +1097,56 @@ const file_buf_validate_conformance_cases_messages_proto_rawDesc = "" + "\x14MessageRequiredOneof\x12C\n" + "\x03val\x18\x01 \x01(\v2'.buf.validate.conformance.cases.TestMsgB\x06\xbaH\x03\xc8\x01\x01H\x00R\x03valB\f\n" + "\x03one\x12\x05\xbaH\x02\b\x01\"\x15\n" + - "\x13MessageWith3dInsideB\x97\x02\n" + + "\x13MessageWith3dInside\"g\n" + + "\x17MessageOneofSingleField\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x10\xbaH\r\"\v\n" + + "\tstr_field\"v\n" + + "\x1aMessageOneofMultipleFields\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x1c\xbaH\x19\"\x17\n" + + "\tstr_field\n" + + "\n" + + "bool_field\"\x80\x01\n" + + "\"MessageOneofMultipleFieldsRequired\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField\x12\x1d\n" + + "\n" + + "bool_field\x18\x02 \x01(\bR\tboolField:\x1e\xbaH\x1b\"\x19\n" + + "\tstr_field\n" + + "\n" + + "bool_field\x10\x01\"G\n" + + "\x1cMessageOneofUnknownFieldName\x12\x1b\n" + + "\tstr_field\x18\x01 \x01(\tR\bstrField:\n" + + "\xbaH\a\"\x05\n" + + "\x03xxxB\x97\x02\n" + "\"com.buf.validate.conformance.casesB\rMessagesProtoP\x01ZFbuf.build/go/protovalidate/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_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_buf_validate_conformance_cases_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_buf_validate_conformance_cases_messages_proto_goTypes = []any{ - (*TestMsg)(nil), // 0: buf.validate.conformance.cases.TestMsg - (*MessageNone)(nil), // 1: buf.validate.conformance.cases.MessageNone - (*MessageDisabled)(nil), // 2: buf.validate.conformance.cases.MessageDisabled - (*Message)(nil), // 3: buf.validate.conformance.cases.Message - (*MessageCrossPackage)(nil), // 4: buf.validate.conformance.cases.MessageCrossPackage - (*MessageSkip)(nil), // 5: buf.validate.conformance.cases.MessageSkip - (*MessageRequired)(nil), // 6: buf.validate.conformance.cases.MessageRequired - (*MessageRequiredButOptional)(nil), // 7: buf.validate.conformance.cases.MessageRequiredButOptional - (*MessageRequiredOneof)(nil), // 8: buf.validate.conformance.cases.MessageRequiredOneof - (*MessageWith3DInside)(nil), // 9: buf.validate.conformance.cases.MessageWith3dInside - (*MessageNone_NoneMsg)(nil), // 10: buf.validate.conformance.cases.MessageNone.NoneMsg - (*other_package.Embed)(nil), // 11: buf.validate.conformance.cases.other_package.Embed + (*TestMsg)(nil), // 0: buf.validate.conformance.cases.TestMsg + (*MessageNone)(nil), // 1: buf.validate.conformance.cases.MessageNone + (*MessageDisabled)(nil), // 2: buf.validate.conformance.cases.MessageDisabled + (*Message)(nil), // 3: buf.validate.conformance.cases.Message + (*MessageCrossPackage)(nil), // 4: buf.validate.conformance.cases.MessageCrossPackage + (*MessageSkip)(nil), // 5: buf.validate.conformance.cases.MessageSkip + (*MessageRequired)(nil), // 6: buf.validate.conformance.cases.MessageRequired + (*MessageRequiredButOptional)(nil), // 7: buf.validate.conformance.cases.MessageRequiredButOptional + (*MessageRequiredOneof)(nil), // 8: buf.validate.conformance.cases.MessageRequiredOneof + (*MessageWith3DInside)(nil), // 9: buf.validate.conformance.cases.MessageWith3dInside + (*MessageOneofSingleField)(nil), // 10: buf.validate.conformance.cases.MessageOneofSingleField + (*MessageOneofMultipleFields)(nil), // 11: buf.validate.conformance.cases.MessageOneofMultipleFields + (*MessageOneofMultipleFieldsRequired)(nil), // 12: buf.validate.conformance.cases.MessageOneofMultipleFieldsRequired + (*MessageOneofUnknownFieldName)(nil), // 13: buf.validate.conformance.cases.MessageOneofUnknownFieldName + (*MessageNone_NoneMsg)(nil), // 14: buf.validate.conformance.cases.MessageNone.NoneMsg + (*other_package.Embed)(nil), // 15: buf.validate.conformance.cases.other_package.Embed } var file_buf_validate_conformance_cases_messages_proto_depIdxs = []int32{ 0, // 0: buf.validate.conformance.cases.TestMsg.nested:type_name -> buf.validate.conformance.cases.TestMsg - 10, // 1: buf.validate.conformance.cases.MessageNone.val:type_name -> buf.validate.conformance.cases.MessageNone.NoneMsg + 14, // 1: buf.validate.conformance.cases.MessageNone.val:type_name -> buf.validate.conformance.cases.MessageNone.NoneMsg 0, // 2: buf.validate.conformance.cases.Message.val:type_name -> buf.validate.conformance.cases.TestMsg - 11, // 3: buf.validate.conformance.cases.MessageCrossPackage.val:type_name -> buf.validate.conformance.cases.other_package.Embed + 15, // 3: buf.validate.conformance.cases.MessageCrossPackage.val:type_name -> buf.validate.conformance.cases.other_package.Embed 0, // 4: buf.validate.conformance.cases.MessageSkip.val:type_name -> buf.validate.conformance.cases.TestMsg 0, // 5: buf.validate.conformance.cases.MessageRequired.val:type_name -> buf.validate.conformance.cases.TestMsg 0, // 6: buf.validate.conformance.cases.MessageRequiredButOptional.val:type_name -> buf.validate.conformance.cases.TestMsg @@ -876,7 +1173,7 @@ func file_buf_validate_conformance_cases_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_buf_validate_conformance_cases_messages_proto_rawDesc), len(file_buf_validate_conformance_cases_messages_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/message_oneof.go b/message_oneof.go new file mode 100644 index 00000000..4ae5fde7 --- /dev/null +++ b/message_oneof.go @@ -0,0 +1,85 @@ +// Copyright 2023-2024 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 protovalidate + +import ( + "fmt" + "strings" + + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// oneofEvaluator is a message evaluator that performs validation on the specified +// fields, ensuring that only one is set. If `required` is true, it enforces that one of +// the fields _must_ be set. +type oneofEvaluator struct { + Fields []protoreflect.FieldDescriptor + Required bool +} + +func (o oneofEvaluator) formatFields() string { + names := make([]string, len(o.Fields)) + for idx, fdesc := range o.Fields { + names[idx] = string(fdesc.Name()) + } + return strings.Join(names, ", ") +} + +func (o oneofEvaluator) Evaluate(_ protoreflect.Message, val protoreflect.Value, cfg *validationConfig) error { + return o.EvaluateMessage(val.Message(), cfg) +} + +func (o oneofEvaluator) EvaluateMessage(msg protoreflect.Message, cfg *validationConfig) error { + if !cfg.filter.ShouldValidate(msg, msg.Descriptor()) { + return nil + } + err := &ValidationError{} + if len(o.Fields) > 0 { + count := 0 + for _, fdesc := range o.Fields { + if msg.Has(fdesc) { + count++ + } + } + + if o.Required && count != 1 { + err.Violations = append(err.Violations, &Violation{ + Proto: &validate.Violation{ + RuleId: proto.String("message.oneof"), + Message: proto.String(fmt.Sprintf("one of %s must be set", o.formatFields())), + }, + }) + return err + } + if count > 1 { + err.Violations = append(err.Violations, &Violation{ + Proto: &validate.Violation{ + RuleId: proto.String("message.oneof"), + Message: proto.String(fmt.Sprintf("only one of %s can be set", o.formatFields())), + }, + }) + return err + } + } + return nil +} + +func (o oneofEvaluator) Tautology() bool { + return false +} + +var _ messageEvaluator = oneofEvaluator{}