Skip to content

Commit

Permalink
Refactor: reduce allocs, remove wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
jchadwick-buf committed Dec 2, 2024
1 parent 6881184 commit 0ba11b5
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 186 deletions.
32 changes: 21 additions & 11 deletions internal/errors/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,39 @@ func Merge(dst, src error, failFast bool) (ok bool, err error) {
}

func FieldPathElement(field protoreflect.FieldDescriptor) *validate.FieldPathElement {
if field == nil {
return nil
}
return &validate.FieldPathElement{
FieldNumber: proto.Int32(int32(field.Number())),
FieldName: proto.String(field.TextName()),
FieldType: descriptorpb.FieldDescriptorProto_Type(field.Kind()).Enum(),
}
}

func FieldPath(field protoreflect.FieldDescriptor) *validate.FieldPath {
if field == nil {
return nil
}
return &validate.FieldPath{
Elements: []*validate.FieldPathElement{
FieldPathElement(field),
},
}
}

// AppendFieldPath appends an element to the end of each field path in err.
// As an exception, if skipSubscript is true, any field paths ending in a
// subscript element will not have a suffix element appended to them.
//
// Note that this function is ordinarily used to append field paths in reverse
// order, as the stack bubbles up through the evaluators. Then, at the end, the
// path is reversed.
func AppendFieldPath(err error, suffix *validate.FieldPathElement, skipSubscript bool) {
func AppendFieldPath(err error, suffix *validate.FieldPathElement) {
if suffix == nil {
return
}
var valErr *ValidationError
if errors.As(err, &valErr) {
for _, violation := range valErr.Violations {
// Special case: Here we skip appending if the last element had a
// subscript. This is a weird special case that makes it
// significantly simpler to handle reverse-constructing paths with
// maps and slices.
if elements := violation.Proto.GetField().GetElements(); skipSubscript &&
len(elements) > 0 && elements[len(elements)-1].Subscript != nil {
continue
}
if violation.Proto.GetField() == nil {
violation.Proto.Field = &validate.FieldPath{}
}
Expand All @@ -96,6 +103,9 @@ func AppendFieldPath(err error, suffix *validate.FieldPathElement, skipSubscript
// is better to avoid the copy instead if possible. This prepend is only used in
// the error case for nested rules (repeated.items, map.keys, map.values.)
func PrependRulePath(err error, prefix []*validate.FieldPathElement) {
if len(prefix) == 0 {
return
}
var valErr *ValidationError
if errors.As(err, &valErr) {
for _, violation := range valErr.Violations {
Expand Down
30 changes: 18 additions & 12 deletions internal/evaluator/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ import (
var (
anyRuleDescriptor = (&validate.FieldConstraints{}).ProtoReflect().Descriptor().Fields().ByName("any")
anyInRuleDescriptor = (&validate.AnyRules{}).ProtoReflect().Descriptor().Fields().ByName("in")
anyInRulePath = []*validate.FieldPathElement{
errors.FieldPathElement(anyRuleDescriptor),
errors.FieldPathElement(anyInRuleDescriptor),
anyInRulePath = &validate.FieldPath{
Elements: []*validate.FieldPathElement{
errors.FieldPathElement(anyRuleDescriptor),
errors.FieldPathElement(anyInRuleDescriptor),
},
}
anyNotInDescriptor = (&validate.AnyRules{}).ProtoReflect().Descriptor().Fields().ByName("not_in")
anyNotInRulePath = []*validate.FieldPathElement{
errors.FieldPathElement(anyRuleDescriptor),
errors.FieldPathElement(anyNotInDescriptor),
anyNotInRulePath = &validate.FieldPath{
Elements: []*validate.FieldPathElement{
errors.FieldPathElement(anyRuleDescriptor),
errors.FieldPathElement(anyNotInDescriptor),
},
}
)

Expand All @@ -41,8 +45,8 @@ var (
// hydrate anyPB's within an expression, breaking evaluation if the type is
// unknown at runtime.
type anyPB struct {
// Descriptor is the FieldDescriptor targeted by this evaluator
Descriptor protoreflect.FieldDescriptor
base base

// TypeURLDescriptor is the descriptor for the TypeURL field
TypeURLDescriptor protoreflect.FieldDescriptor
// In specifies which type URLs the value may possess
Expand All @@ -63,12 +67,13 @@ func (a anyPB) Evaluate(val protoreflect.Value, failFast bool) error {
if _, ok := a.In[typeURL]; !ok {
err.Violations = append(err.Violations, &errors.Violation{
Proto: &validate.Violation{
Rule: &validate.FieldPath{Elements: anyInRulePath},
Field: a.base.fieldPath(),
Rule: a.base.rulePath(anyInRulePath),
ConstraintId: proto.String("any.in"),
Message: proto.String("type URL must be in the allow list"),
},
FieldValue: val,
FieldDescriptor: a.Descriptor,
FieldDescriptor: a.base.Descriptor,
RuleValue: a.InValue,
RuleDescriptor: anyInRuleDescriptor,
})
Expand All @@ -82,12 +87,13 @@ func (a anyPB) Evaluate(val protoreflect.Value, failFast bool) error {
if _, ok := a.NotIn[typeURL]; ok {
err.Violations = append(err.Violations, &errors.Violation{
Proto: &validate.Violation{
Rule: &validate.FieldPath{Elements: anyNotInRulePath},
Field: a.base.fieldPath(),
Rule: a.base.rulePath(anyNotInRulePath),
ConstraintId: proto.String("any.not_in"),
Message: proto.String("type URL must not be in the block list"),
},
FieldValue: val,
FieldDescriptor: a.Descriptor,
FieldDescriptor: a.base.Descriptor,
RuleValue: a.NotInValue,
RuleDescriptor: anyNotInDescriptor,
})
Expand Down
90 changes: 90 additions & 0 deletions internal/evaluator/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// 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 evaluator

import (
"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"github.com/bufbuild/protovalidate-go/internal/errors"
"google.golang.org/protobuf/reflect/protoreflect"
)

// base is a common struct used by all field evaluators. It holds
// some common information used across all field evaluators.
type base struct {
// Descriptor is the FieldDescriptor targeted by this evaluator, nor nil if
// there is none.
Descriptor protoreflect.FieldDescriptor

// FieldPatht is the field path element that pertains to this evaluator, or
// nil if there is none.
FieldPathElement *validate.FieldPathElement

// RulePrefix is a static prefix this evaluator should add to the rule path
// of violations.
RulePrefix *validate.FieldPath
}

func newBase(valEval *value) base {
return base{
Descriptor: valEval.Descriptor,
FieldPathElement: errors.FieldPathElement(valEval.Descriptor),
RulePrefix: rulePrefixForNesting(valEval.Nested),
}
}

func (b *base) fieldPath() *validate.FieldPath {
if b.FieldPathElement == nil {
return nil
}
return &validate.FieldPath{
Elements: []*validate.FieldPathElement{
b.FieldPathElement,
},
}
}

func (b *base) rulePath(suffix *validate.FieldPath) *validate.FieldPath {
return prefixRulePath(b.RulePrefix, suffix)
}

func rulePrefixForNesting(typ nestedType) *validate.FieldPath {
switch typ {
case nestedNone:
return nil
case nestedRepeatedItem:
return repeatedItemsRulePath
case nestedMapKey:
return mapKeysRulePath
case nestedMapValue:
return mapValuesRulePath
default:
return nil
}
}

func prefixRulePath(prefix *validate.FieldPath, suffix *validate.FieldPath) *validate.FieldPath {
if len(prefix.GetElements()) > 0 {
return &validate.FieldPath{
Elements: append(
append(
[]*validate.FieldPathElement{},
prefix.GetElements()...,
),
suffix.GetElements()...,
),
}
}
return suffix
}
Loading

0 comments on commit 0ba11b5

Please sign in to comment.