Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement predefined field constraints #144

Merged
merged 16 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 19 additions & 0 deletions internal/cmd/protovalidate-conformance-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ func TestConformance(req *harness.TestConformanceRequest) (*harness.TestConforma
err = fmt.Errorf("failed to parse file descriptors: %w", err)
return nil, err
}
// Register all dynamic FDS files and their extensions.
files.RangeFiles(func(file protoreflect.FileDescriptor) bool {
if _, err := protoregistry.GlobalFiles.FindFileByPath(file.Path()); err == nil {
// Already registered
return true
}
err = protoregistry.GlobalFiles.RegisterFile(file)
if err != nil {
err = fmt.Errorf("failed to register file descriptor %q: %w", file.FullName(), err)
}
exts := file.Extensions()
for i := 0; i < exts.Len(); i++ {
protoregistry.GlobalTypes.RegisterExtension(dynamicpb.NewExtensionType(exts.Get(i)))
}
return err == nil
})
if err != nil {
return nil, err
}
val, err := protovalidate.New()
if err != nil {
err = fmt.Errorf("failed to initialize validator: %w", err)
Expand Down
43 changes: 39 additions & 4 deletions internal/constraints/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
package constraints

import (
"fmt"

"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/priv"
"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared"

Check failure on line 21 in internal/constraints/cache.go

View workflow job for this annotation

GitHub Actions / Go (1.21.x)

no required module provides package buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared; to add it:

Check failure on line 21 in internal/constraints/cache.go

View workflow job for this annotation

GitHub Actions / Go (stable)

no required module provides package buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared; to add it:

Check failure on line 21 in internal/constraints/cache.go

View workflow job for this annotation

GitHub Actions / Go (1.22.x)

no required module provides package buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared; to add it:

Check failure on line 21 in internal/constraints/cache.go

View workflow job for this annotation

GitHub Actions / Go (oldstable)

no required module provides package buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared; to add it:
"github.com/bufbuild/protovalidate-go/celext"
"github.com/bufbuild/protovalidate-go/internal/errors"
"github.com/bufbuild/protovalidate-go/internal/expression"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)

// Cache is a build-through cache to computed standard constraints.
Expand Down Expand Up @@ -57,8 +61,19 @@
}

var asts expression.ASTSet
constraints.Range(func(desc protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
precomputedASTs, compileErr := c.loadOrCompileStandardConstraint(env, desc)
constraints.Range(func(desc protoreflect.FieldDescriptor, rule protoreflect.Value) bool {
fieldEnv, compileErr := env.Extend(
cel.Constant(
"rule",
celext.ProtoFieldToCELType(desc, true, forItems),
types.DefaultTypeAdapter.NativeToValue(rule.Interface()),
),
)
if err != nil {
jchadwick-buf marked this conversation as resolved.
Show resolved Hide resolved
err = compileErr
return false
}
precomputedASTs, compileErr := c.loadOrCompileStandardConstraint(fieldEnv, desc)
if compileErr != nil {
err = compileErr
return false
Expand Down Expand Up @@ -102,6 +117,11 @@
return nil, true, nil
}
rules = constraints.Get(setOneof).Message()
// Reparse unrecognized fields so that we get dynamic extensions.
err = reparseUnrecognized(rules)
if err != nil {
return nil, false, fmt.Errorf("error reparsing message: %v", err)
}
return rules, false, nil
}

Expand Down Expand Up @@ -137,7 +157,7 @@
if cachedConstraint, ok := c.cache[constraintFieldDesc]; ok {
return cachedConstraint, nil
}
exprs, _ := proto.GetExtension(constraintFieldDesc.Options(), priv.E_Field).(*priv.FieldConstraints)
exprs, _ := proto.GetExtension(constraintFieldDesc.Options(), shared.E_Field).(*shared.FieldConstraints)
set, err = expression.CompileASTs(exprs.GetCel(), env)
if err != nil {
return set, errors.NewCompilationErrorf(
Expand Down Expand Up @@ -170,3 +190,18 @@
return expected, ok
}
}

func reparseUnrecognized(reflectMessage protoreflect.Message) error {
unknown := reflectMessage.GetUnknown()
if len(unknown) > 0 {
reflectMessage.SetUnknown(nil)
options := proto.UnmarshalOptions{
Resolver: protoregistry.GlobalTypes,
Merge: true,
}
if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil {
return err
}
}
return nil
}
Loading