Skip to content

Commit

Permalink
oneof constraints' required now checks whether scalar fields are empty
Browse files Browse the repository at this point in the history
  • Loading branch information
oliversun9 committed Nov 1, 2023
1 parent 51ba86b commit ca20ea3
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 41 deletions.
26 changes: 20 additions & 6 deletions internal/evaluator/oneof.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,26 @@ func (o oneof) Evaluate(val protoreflect.Value, failFast bool) error {
}

func (o oneof) EvaluateMessage(msg protoreflect.Message, _ bool) error {
if o.Required && msg.WhichOneof(o.Descriptor) == nil {
return &errors.ValidationError{Violations: []*validate.Violation{{
FieldPath: string(o.Descriptor.Name()),
ConstraintId: "required",
Message: "exactly one field is required in oneof",
}}}
if !o.Required {
return nil
}
validationErr := &errors.ValidationError{Violations: []*validate.Violation{{
FieldPath: string(o.Descriptor.Name()),
ConstraintId: "required",
Message: "exactly one field is required in oneof",
}}}
presentFieldDescriptor := msg.WhichOneof(o.Descriptor)
if presentFieldDescriptor == nil {
return validationErr
}
if presentFieldDescriptor.Kind() == protoreflect.GroupKind || presentFieldDescriptor.Kind() == protoreflect.MessageKind {
if !msg.Get(presentFieldDescriptor).Message().IsValid() {
return validationErr
}
return nil
}
if msg.Get(presentFieldDescriptor).Equal(presentFieldDescriptor.Default()) {
return validationErr
}
return nil
}
Expand Down
176 changes: 141 additions & 35 deletions internal/gen/tests/example/v1/validations.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions proto/tests/example/v1/validations.proto
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,11 @@ message FieldOfTypeAny {
expression: "this == this"
}];
}

message RequiredOneof {
oneof required_oneof {
option (buf.validate.oneof).required = true;
string fld_1 = 1;
google.protobuf.FieldMask fld_2 = 2;
}
}
62 changes: 62 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,65 @@ func TestValidator_Validate_FieldOfTypeAny(t *testing.T) {
err = val.Validate(msg)
require.NoError(t, err)
}

func TestValidator_RequiredOneof(t *testing.T) {
t.Parallel()
val, err := New()
require.NoError(t, err)

tests := []struct {
msg *pb.RequiredOneof
exErr bool
}{
{
&pb.RequiredOneof{},
true,
},
{
&pb.RequiredOneof{
RequiredOneof: &pb.RequiredOneof_Fld_2{
// nil FieldMask is empty.
Fld_2: nil,
},
},
true,
},
{
&pb.RequiredOneof{
RequiredOneof: &pb.RequiredOneof_Fld_2{
// this passes `required`
Fld_2: &fieldmaskpb.FieldMask{},
},
},
false,
},
{
&pb.RequiredOneof{
RequiredOneof: &pb.RequiredOneof_Fld_1{
// empty string does not pass `required` as a normal field,
// and it shouldn't pass it here either.
Fld_1: "",
},
},
true,
},
{
&pb.RequiredOneof{
RequiredOneof: &pb.RequiredOneof_Fld_1{
// passes `required`
Fld_1: "foo",
},
},
false,
},
}
for _, test := range tests {
test := test
err = val.Validate(test.msg)
if test.exErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}

0 comments on commit ca20ea3

Please sign in to comment.