Skip to content

Commit

Permalink
Merge pull request #54 from bufbuild/croche/tcn-1583-refactor-evaluator
Browse files Browse the repository at this point in the history
Simplify evaluators
  • Loading branch information
rodaine authored Apr 13, 2023
2 parents 105acdd + 4eda0ac commit 8c1b342
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 171 deletions.
129 changes: 64 additions & 65 deletions go/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,111 +15,110 @@
package protovalidate

import (
validatev2 "github.com/bufbuild/protovalidate/go/gen/buf/validate"
"github.com/bufbuild/protovalidate/go/gen/buf/validate"
"google.golang.org/protobuf/reflect/protoreflect"
)

type evaluator interface {
evaluate(msg protoreflect.Message, failFast bool) error
evaluate(val protoreflect.Value, failFast bool) error
}

type messageEvaluator struct {
err error
constraints []evaluator
type messageEvaluator interface {
evaluateMessage(msg protoreflect.Message, failFast bool) error
}

func (m *messageEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
if err := m.err; err != nil {
return err
type constraintsEval []compiledExpression

func (c constraintsEval) evaluate(val protoreflect.Value, failFast bool) error {
iface := val.Interface()
if msg, ok := iface.(protoreflect.Message); ok {
return c.eval(msg.Interface(), failFast)
}
return c.eval(iface, failFast)
}

func (c constraintsEval) evaluateMessage(msg protoreflect.Message, failFast bool) error {
return c.eval(msg.Interface(), failFast)
}

func (c constraintsEval) eval(val any, failFast bool) error {
binding := namedBinding{name: "this", val: val}
var (
err error
ok bool
)
for _, constraint := range m.constraints {
evalErr := constraint.evaluate(msg, failFast)
for _, expr := range c {
evalErr := expr.eval(binding)
if err, ok = mergeErrors(err, evalErr, failFast); !ok {
break
}
}
return err
}

type messageExpressionEvaluator struct {
exprs []compiledExpression
}

func (m messageExpressionEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
binding := namedBinding{name: "this", val: msg.Interface()}
return evalExprs(m.exprs, binding, failFast)
type messageEval struct {
err error
constraints []messageEvaluator
}

type fieldExpressionEvaluator struct {
field protoreflect.FieldDescriptor
exprs []compiledExpression
func (m *messageEval) evaluate(val protoreflect.Value, failFast bool) error {
return m.evaluateMessage(val.Message(), failFast)
}

func (f fieldExpressionEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
binding := namedBinding{name: "this", val: msg.Get(f.field)}
err := evalExprs(f.exprs, binding, failFast)
if valErr, ok := err.(*ValidationError); ok {
valErr.prefixPaths(string(f.field.Name()), ".")
func (m *messageEval) evaluateMessage(msg protoreflect.Message, failFast bool) error {
if err := m.err; err != nil {
return err
}
return err
}

type messageFieldEvaluator struct {
field protoreflect.FieldDescriptor
embeddedMessageEvaluator
}

func (m messageFieldEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
fldMsg := msg.Get(m.field).Message()
err := m.embeddedMessageEvaluator.evaluate(fldMsg, failFast)
if valErr, ok := err.(*ValidationError); ok {
valErr.prefixPaths(string(m.field.FullName()), ".")
var (
err error
ok bool
)
for _, constraint := range m.constraints {
evalErr := constraint.evaluateMessage(msg, failFast)
if err, ok = mergeErrors(err, evalErr, failFast); !ok {
break
}
}
return err
}

type embeddedMessageEvaluator struct {
required bool
skipped bool

msgEval *messageEvaluator
exprs []compiledExpression
type singularFieldEval struct {
required bool
ignoreEmpty bool
field protoreflect.FieldDescriptor
constraints []evaluator
}

func (e embeddedMessageEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
if e.required && !msg.IsValid() {
return &ValidationError{Violations: []*validatev2.Violation{{
func (f singularFieldEval) evaluateMessage(msg protoreflect.Message, failFast bool) (err error) {
if f.ignoreEmpty && !msg.Has(f.field) {
return nil
}
defer func() {
if valErr, ok := err.(*ValidationError); ok {
valErr.prefixPaths(string(f.field.Name()), ".")
}
}()
if f.required && !msg.Has(f.field) {
return &ValidationError{Violations: []*validate.Violation{{
ConstraintId: "required",
Message: "value is required",
}}}
}

var (
err error
ok bool
)
if !e.skipped && msg.IsValid() {
evalErr := e.msgEval.evaluate(msg, failFast)
err, ok = mergeErrors(err, evalErr, failFast)
if !ok {
val := msg.Get(f.field)
ok := false
for _, constraint := range f.constraints {
evalErr := constraint.evaluate(val, failFast)
if err, ok = mergeErrors(err, evalErr, failFast); !ok {
return err
}
}

binding := namedBinding{name: "this", val: msg.Interface()}
evalErr := evalExprs(e.exprs, binding, failFast)
err, _ = mergeErrors(err, evalErr, failFast)
return err
}

var (
_ evaluator = (*messageEvaluator)(nil)
_ evaluator = messageExpressionEvaluator{}
_ evaluator = fieldExpressionEvaluator{}
_ evaluator = messageFieldEvaluator{}
_ evaluator = embeddedMessageEvaluator{}
_ evaluator = constraintsEval(nil)
_ messageEvaluator = constraintsEval(nil)
_ evaluator = (*messageEval)(nil)
_ messageEvaluator = (*messageEval)(nil)
_ messageEvaluator = singularFieldEval{}
)
18 changes: 2 additions & 16 deletions go/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,15 @@ import (
"github.com/google/cel-go/interpreter"
)

type Expression interface {
type expression interface {
GetId() string
GetMessage() string
GetExpression() string
}

func evalExprs(exprs []compiledExpression, binding interpreter.Activation, failFast bool) error {
var (
err error
ok bool
)
for _, expr := range exprs {
evalErr := expr.eval(binding)
if err, ok = mergeErrors(err, evalErr, failFast); !ok {
break
}
}
return err
}

type compiledExpression struct {
program cel.Program
source Expression
source expression
}

func (expr compiledExpression) eval(bindings interpreter.Activation) error {
Expand Down
64 changes: 35 additions & 29 deletions go/gen/example/v1/validations.pb.go

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

Loading

0 comments on commit 8c1b342

Please sign in to comment.