Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.26.0

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1
buf.build/go/protovalidate v1.2.0
buf.build/go/protovalidate v1.3.0
github.com/Oudwins/zog v0.22.2
github.com/amacneil/dbmate/v2 v2.34.1
github.com/aws/aws-sdk-go-v2 v1.43.4
Expand Down Expand Up @@ -122,7 +122,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/cel-go v0.28.0 // indirect
github.com/google/cel-go v0.30.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-202607092007
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0=
buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4=
buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms=
buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs=
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
Expand Down Expand Up @@ -220,6 +222,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc=
github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo=
github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand Down
15 changes: 11 additions & 4 deletions vendor/buf.build/go/protovalidate/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ GOLANGCI_LINT_VERSION ?= v2.9.0
# 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.2.0
LICENSE_IGNORE := -e .github/ -e .golangci.yml -e buf.gen.yaml -e buf.yaml -e conformance/expected_failures.yaml
BUF_VERSION := 1.69.0

.PHONY: help
help: ## Describe useful make targets
Expand All @@ -32,7 +34,7 @@ clean: ## Delete intermediate build artifacts
git clean -Xdf

.PHONY: test
test: ## Run all unit tests
test: ## Run all unit tests with and without native rules
$(GO) test -race -cover ./...

.PHONY: test-opaque
Expand All @@ -59,6 +61,7 @@ lint-fix:
.PHONY: conformance
conformance: $(BIN)/protovalidate-conformance protovalidate-conformance-go ## Run conformance tests
$(BIN)/protovalidate-conformance $(ARGS) $(BIN)/protovalidate-conformance-go --expected_failures=conformance/expected_failures.yaml
DISABLE_NATIVE_RULES=true $(BIN)/protovalidate-conformance $(ARGS) $(BIN)/protovalidate-conformance-go --expected_failures=conformance/expected_failures.yaml

.PHONY: conformance-hyperpb
conformance-hyperpb: ## Run conformance tests against hyperpb
Expand All @@ -79,7 +82,8 @@ generate-license: $(BIN)/license-header
$(BIN)/license-header \
--license-type apache \
--copyright-holder "Buf Technologies, Inc." \
--year-range "$(COPYRIGHT_YEARS)"
--year-range "$(COPYRIGHT_YEARS)" \
$(LICENSE_IGNORE)

.PHONY: checkgenerate
checkgenerate: generate
Expand All @@ -98,6 +102,9 @@ bench: $(BENCH_TMP)
-count $(BENCH_COUNT) \
| tee "$(BENCH_TMP)/$(BENCH_NAME).bench.txt"

.PHONY: bench-cel
bench-cel: $(BENCH_TMP)
DISABLE_NATIVE_RULES=true $(MAKE) bench

.PHONY: upgrade-go
upgrade-go:
Expand All @@ -110,11 +117,11 @@ $(BIN):
@mkdir -p $(BIN)

$(BIN)/buf: $(BIN) Makefile
GOBIN=$(abspath $(@D)) $(GO) install github.com/bufbuild/buf/cmd/buf@v1.67.0
GOBIN=$(abspath $(@D)) $(GO) install github.com/bufbuild/buf/cmd/buf@v$(BUF_VERSION)

$(BIN)/license-header: $(BIN) Makefile
GOBIN=$(abspath $(@D)) $(GO) install \
github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v1.67.0
github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v$(BUF_VERSION)

$(BIN)/golangci-lint: $(BIN) Makefile
GOBIN=$(abspath $(@D)) $(GO) install \
Expand Down
6 changes: 6 additions & 0 deletions vendor/buf.build/go/protovalidate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ Highlights for Go developers include:

API documentation for Go is available on [pkg.go.dev][pkg-go].

### Native standard validation rules
protovalidate-go provides native support for standard validation rule processing. They are enabled by default and are disabled by setting the ValidatorOption `WithDisableNativeRules`.

We continue to validate that the native rules and the CEL rules produce identical results. The `compliance` Makefile target has been updated to run twice,
once with the native rules enabled, and once with the CEL rules enabled.

## Additional languages and repositories

Protovalidate isn't just for Go! You might be interested in sibling repositories for other languages:
Expand Down
7 changes: 6 additions & 1 deletion vendor/buf.build/go/protovalidate/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ func (set astSet) ReduceResiduals(rules protoreflect.Message, opts ...cel.Progra
residuals = append(residuals, ast)
continue
}
val, details, _ := program.Program.Eval(activation)
partialAct, err := cel.PartialVars(activation, cel.AttributePattern("this"))
if err != nil {
residuals = append(residuals, ast)
continue
}
val, details, _ := program.Program.Eval(partialAct)
if val != nil {
switch value := val.Value().(type) {
case bool:
Expand Down
92 changes: 92 additions & 0 deletions vendor/buf.build/go/protovalidate/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"slices"

"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)

Expand Down Expand Up @@ -60,6 +61,97 @@ func (b *base) rulePath(suffix *validate.FieldPath) *validate.FieldPath {
return prefixRulePath(b.RulePrefix, suffix)
}

// ruleSite is a compile-time pre-built bundle for a single rule site: the
// 2-element rule path suffix and the leaf descriptor. Using ruleSite with
// newViolationAt avoids re-allocating FieldPathElement proto messages on
// every violation (each FieldPathElement rebuild allocates ~4 sub-objects).
type ruleSite struct {
// pathElements is the rule path suffix, e.g.
// [FieldRules.int32 element, Int32Rules.gt element].
//
// Safe to share across violations: only the containing *FieldPath is
// rebuilt per violation, which is what updateViolationPaths mutates.
pathElements []*validate.FieldPathElement
// desc is the leaf rule field descriptor (e.g. Int32Rules.gt); it is
// stored on the returned *Violation's RuleDescriptor field.
desc protoreflect.FieldDescriptor

// if there are constant values for ruleID or message, specify them once so they can be reused
ruleID *string
message *string
}

// makeRuleSite pre-builds a ruleSite for a rule at compile time.
// if the ruleID or message are NOT constant, pass in an empty string and supply it when calling newViolation.
func makeRuleSite(ruleDesc, desc protoreflect.FieldDescriptor, ruleID string, message string) ruleSite {
var ruleIDPtr *string
if ruleID != "" {
ruleIDPtr = proto.String(ruleID)
}
var messagePtr *string
if message != "" {
messagePtr = proto.String(message)
}
return ruleSite{
pathElements: []*validate.FieldPathElement{
fieldPathElement(ruleDesc),
fieldPathElement(desc),
},
desc: desc,
ruleID: ruleIDPtr,
message: messagePtr,
}
}

// newViolation constructs a Violation.
// ruleDesc is the top-level rule descriptor (e.g., FieldRules.int32),
// desc is the specific constraint descriptor (e.g., Int32Rules.gt).
//
// it uses pre-built path elements in site instead of rebuilding them each call.
func (b *base) newViolation(
site ruleSite,
ruleID string,
message string,
fieldValue protoreflect.Value,
ruleValue protoreflect.Value,
) *Violation {
ruleIDPtr := site.ruleID
if ruleIDPtr == nil {
ruleIDPtr = proto.String(ruleID)
}
messagePtr := site.message
if messagePtr == nil {
messagePtr = proto.String(message)
}
return &Violation{
Proto: validate.Violation_builder{
Field: b.fieldPath(),
Rule: b.rulePath(validate.FieldPath_builder{
Elements: site.pathElements,
}.Build()),
RuleId: ruleIDPtr,
Message: messagePtr,
}.Build(),
FieldValue: fieldValue,
FieldDescriptor: b.Descriptor,
RuleValue: ruleValue,
RuleDescriptor: site.desc,
}
}

func sliceToListValue[T any](
msg proto.Message,
desc protoreflect.FieldDescriptor,
vals []T,
conv func(T) protoreflect.Value,
) protoreflect.Value {
list := msg.ProtoReflect().Mutable(desc).List()
for _, val := range vals {
list.Append(conv(val))
}
return protoreflect.ValueOfList(list)
}

func prefixRulePath(prefix *validate.FieldPath, suffix *validate.FieldPath) *validate.FieldPath {
if len(prefix.GetElements()) > 0 {
return validate.FieldPath_builder{
Expand Down
99 changes: 99 additions & 0 deletions vendor/buf.build/go/protovalidate/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type builder struct {
extensionTypeResolver protoregistry.ExtensionTypeResolver
allowUnknownFields bool
Load func(desc protoreflect.MessageDescriptor) messageEvaluator
disableNativeRules bool
}

// newBuilder initializes a new Builder.
Expand All @@ -57,13 +58,15 @@ func newBuilder(
disableLazy bool,
extensionTypeResolver protoregistry.ExtensionTypeResolver,
allowUnknownFields bool,
disableNativeRules bool,
seedDesc ...protoreflect.MessageDescriptor,
) *builder {
bldr := &builder{
env: env,
rules: newCache(),
extensionTypeResolver: extensionTypeResolver,
allowUnknownFields: allowUnknownFields,
disableNativeRules: disableNativeRules,
}

if disableLazy {
Expand Down Expand Up @@ -464,6 +467,37 @@ func (bldr *builder) processStandardRules(
}
}

// make a copy of the rules because we're going to clear anything handled by native rules, leaving
// anything else to be handled by CEL rules. this allows us to fall back to CEL rules if there is no
// native rule (for example, when a new rule is added to the validate proto, but the native code hasn't
// been updated. we are making a copy because we don't want to modify the original rules in case they are
// reused (happens in some test cases, could happen in production code with dynamic messages).
rules = proto.CloneOf[*validate.FieldRules](rules)

// put behind a feature flag to allow for testing.
// it's easier to follow like this, don't break it up
//nolint:nestif
if !bldr.disableNativeRules {
// Try native Go evaluators for repeated list-level rules (min_items, max_items, unique).
if fdesc.IsList() && valEval.NestedRule == nil {
if native := tryNativeRepeatedRules(newBase(valEval), rules.GetRepeated()); native != nil {
valEval.Append(native)
}
}
// Try native Go evaluators for map-level rules (min_pairs, max_pairs).
if fdesc.IsMap() && valEval.NestedRule == nil {
if native := tryNativeMapRules(newBase(valEval), rules.GetMap()); native != nil {
valEval.Append(native)
}
}
// Try native Go evaluators for known simple rules before falling back to CEL.
if !fdesc.IsMap() && !fdesc.IsList() {
if native := bldr.tryNativeRules(fdesc, rules, valEval); native != nil {
valEval.Append(native)
}
}
}

stdRules, err := bldr.rules.Build(
bldr.env,
fdesc,
Expand All @@ -482,6 +516,71 @@ func (bldr *builder) processStandardRules(
return nil
}

func (bldr *builder) tryNativeRules(
fdesc protoreflect.FieldDescriptor,
rules *validate.FieldRules,
valEval *value,
) evaluator {
if rules == nil {
return nil
}
base := newBase(valEval)
var native evaluator
switch fdesc.Kind() {
case protoreflect.Int32Kind:
native = tryBuildNativeInt32Rules(base, rules.GetInt32())
case protoreflect.Sint32Kind:
native = tryBuildNativeSint32Rules(base, rules.GetSint32())
case protoreflect.Sfixed32Kind:
native = tryBuildNativeSfixed32Rules(base, rules.GetSfixed32())
case protoreflect.Int64Kind:
native = tryBuildNativeInt64Rules(base, rules.GetInt64())
case protoreflect.Sint64Kind:
native = tryBuildNativeSint64Rules(base, rules.GetSint64())
case protoreflect.Sfixed64Kind:
native = tryBuildNativeSfixed64Rules(base, rules.GetSfixed64())
case protoreflect.Uint32Kind:
native = tryBuildNativeUint32Rules(base, rules.GetUint32())
case protoreflect.Fixed32Kind:
native = tryBuildNativeFixed32Rules(base, rules.GetFixed32())
case protoreflect.Uint64Kind:
native = tryBuildNativeUint64Rules(base, rules.GetUint64())
case protoreflect.Fixed64Kind:
native = tryBuildNativeFixed64Rules(base, rules.GetFixed64())
case protoreflect.FloatKind:
native = tryBuildNativeFloatRules(base, rules.GetFloat())
case protoreflect.DoubleKind:
native = tryBuildNativeDoubleRules(base, rules.GetDouble())
case protoreflect.StringKind:
native = tryBuildNativeStringRules(base, rules.GetString())
case protoreflect.BoolKind:
native = tryBuildNativeBoolRules(base, rules.GetBool())
case protoreflect.EnumKind:
native = tryBuildNativeEnumRules(base, rules.GetEnum())
case protoreflect.BytesKind:
native = tryBuildNativeBytesRules(base, rules.GetBytes())
default:
return nil
}
if native == nil {
return nil
}
// processWrapperRules swaps in the inner "value" field as fdesc when
// building rules for a wrapper WKT (Int32Value, StringValue, ...), but
// leaves valEval.Descriptor pointing at the outer wrapper message field.
// Detect that here and wrap the native eval so it unwraps the wrapper
// message at runtime before calling val.Int()/Bytes()/etc.
if valEval.Descriptor != nil &&
(valEval.Descriptor.Kind() == protoreflect.MessageKind ||
valEval.Descriptor.Kind() == protoreflect.GroupKind) {
native = wrappedValueEval{
innerField: fdesc,
inner: native,
}
}
return native
}

func (bldr *builder) processAnyRules(
fdesc protoreflect.FieldDescriptor,
fieldRules *validate.FieldRules,
Expand Down
Loading