Skip to content

Commit

Permalink
Unify type adapter / provider representations
Browse files Browse the repository at this point in the history
The ref.TypeAdapter, ref.TypeProvider, and ref.TypeRegistry have
been deprecated in favor of types.Adapter, types.Provider, and
types.Registry respectively.

All existing uses of ref.TypeProvider / ref.TypeAdapter interfaces
are wrapped into an interop layer under the covers which ensures
that all internal objects refer to the new types and interfaces.

This shift marks the deprecation of the last of the exprpb.Type
methods, thus simplifying implementation of custom type providers
as implementers need only consider the *types.Type and not the
ref.Type, exprpb.Type, or any other alternative.
  • Loading branch information
TristonianJones committed Jul 12, 2023
1 parent 152a561 commit 57a22de
Show file tree
Hide file tree
Showing 38 changed files with 1,127 additions and 383 deletions.
3 changes: 3 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,9 @@ func TestOptionalValuesEval(t *testing.T) {
Variable("y", OptionalType(IntType)),
Variable("z", IntType),
)
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
}
adapter := env.TypeAdapter()
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
Expand Down
119 changes: 110 additions & 9 deletions cel/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,16 @@ func (ast *Ast) Source() Source {

// FormatType converts a type message into a string representation.
//
// Deprecated: prefer FormatCelType
// Deprecated: prefer FormatCELType
func FormatType(t *exprpb.Type) string {
return checker.FormatCheckedType(t)
}

// FormatCelType formats a cel.Type value to a string representation.
// FormatCELType formats a cel.Type value to a string representation.
//
// The type formatting is identical to FormatType.
func FormatCelType(t *Type) string {
return checker.FormatCelType(t)
func FormatCELType(t *Type) string {
return checker.FormatCELType(t)
}

// Env encapsulates the context necessary to perform parsing, type checking, or generation of
Expand All @@ -113,8 +113,8 @@ type Env struct {
variables []*decls.VariableDecl
functions map[string]*decls.FunctionDecl
macros []parser.Macro
adapter ref.TypeAdapter
provider ref.TypeProvider
adapter types.Adapter
provider types.Provider
features map[int]bool
appliedFeatures map[int]bool
libraries map[string]bool
Expand Down Expand Up @@ -314,8 +314,8 @@ func (e *Env) Extend(opts ...EnvOption) (*Env, error) {
// Copy the adapter / provider if they appear to be mutable.
adapter := e.adapter
provider := e.provider
adapterReg, isAdapterReg := e.adapter.(ref.TypeRegistry)
providerReg, isProviderReg := e.provider.(ref.TypeRegistry)
adapterReg, isAdapterReg := e.adapter.(*types.Registry)
providerReg, isProviderReg := e.provider.(*types.Registry)
// In most cases the provider and adapter will be a ref.TypeRegistry;
// however, in the rare cases where they are not, they are assumed to
// be immutable. Since it is possible to set the TypeProvider separately
Expand Down Expand Up @@ -439,14 +439,31 @@ func (e *Env) Program(ast *Ast, opts ...ProgramOption) (Program, error) {
return newProgram(e, ast, optSet)
}

// CELTypeAdapter returns the `types.Adapter` configured for the environment.
func (e *Env) CELTypeAdapter() types.Adapter {
return e.adapter
}

// CELTypeProvider returns the `types.Provider` configured for the environment.
func (e *Env) CELTypeProvider() types.Provider {
return e.provider
}

// TypeAdapter returns the `ref.TypeAdapter` configured for the environment.
//
// Deprecated: use CELTypeAdapter()
func (e *Env) TypeAdapter() ref.TypeAdapter {
return e.adapter
}

// TypeProvider returns the `ref.TypeProvider` configured for the environment.
//
// Deprecated: use CELTypeProvider()
func (e *Env) TypeProvider() ref.TypeProvider {
return e.provider
if legacyProvider, ok := e.provider.(ref.TypeProvider); ok {
return legacyProvider
}
return &interopLegacyTypeProvider{Provider: e.provider}
}

// UnknownVars returns an interpreter.PartialActivation which marks all variables
Expand Down Expand Up @@ -740,6 +757,90 @@ func getStdEnv() (*Env, error) {
return stdEnv, stdEnvErr
}

// interopCELTypeProvider layers support for the types.Provider interface on top of a ref.TypeProvider.
type interopCELTypeProvider struct {
ref.TypeProvider
}

// FindStructType returns a types.Type instance for the given fully-qualified typeName if one exists.
//
// This method proxies to the underyling ref.TypeProvider's FindType method and converts protobuf type
// into a native type representation. If the conversion fails, the type is listed as not found.
func (p *interopCELTypeProvider) FindStructType(typeName string) (*types.Type, bool) {
if et, found := p.FindType(typeName); found {
t, err := types.ExprTypeToType(et)
if err != nil {
return nil, false
}
return t, true
}
return nil, false
}

// FindStructFieldType returns a types.FieldType instance for the given fully-qualified typeName and field
// name, if one exists.
//
// This method proxies to the underyling ref.TypeProvider's FindFieldType method and converts protobuf type
// into a native type representation. If the conversion fails, the type is listed as not found.
func (p *interopCELTypeProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) {
if ft, found := p.FindFieldType(structType, fieldName); found {
t, err := types.ExprTypeToType(ft.Type)
if err != nil {
return nil, false
}
return &types.FieldType{
Type: t,
IsSet: ft.IsSet,
GetFrom: ft.GetFrom,
}, true
}
return nil, false
}

// interopLegacyTypeProvider layers support for the ref.TypeProvider interface on top of a types.Provider.
type interopLegacyTypeProvider struct {
types.Provider
}

// FindType retruns the protobuf Type representation for the input type name if one exists.
//
// This method proxies to the underlying types.Provider FindStructType method and converts the types.Type
// value to a protobuf Type representation.
//
// Failure to convert the type will result in the type not being found.
func (p *interopLegacyTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
if t, found := p.FindStructType(typeName); found {
et, err := types.TypeToExprType(t)
if err != nil {
return nil, false
}
return et, true
}
return nil, false
}

// FindFieldType returns the protobuf-based FieldType representation for the input type name and field,
// if one exists.
//
// This call proxies to the types.Provider FindStructFieldType method and converts the types.FIeldType
// value to a protobuf-based ref.FieldType representation if found.
//
// Failure to convert the FieldType will result in the field not being found.
func (p *interopLegacyTypeProvider) FindFieldType(structType, fieldName string) (*ref.FieldType, bool) {
if cft, found := p.FindStructFieldType(structType, fieldName); found {
et, err := types.TypeToExprType(cft.Type)
if err != nil {
return nil, false
}
return &ref.FieldType{
Type: et,
IsSet: cft.IsSet,
GetFrom: cft.GetFrom,
}, true
}
return nil, false
}

var (
stdEnvInit sync.Once
stdEnv *Env
Expand Down
141 changes: 138 additions & 3 deletions cel/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import (
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/test/proto3pb"

exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)

func TestIssuesNil(t *testing.T) {
Expand Down Expand Up @@ -84,7 +88,7 @@ ERROR: <input>:1:2: Syntax error: mismatched input '<EOF>' expecting {'[', '{',
}
}

func TestFormatCelTypeEquivalence(t *testing.T) {
func TestFormatCELTypeEquivalence(t *testing.T) {
values := []*Type{
AnyType,
MapType(StringType, DynType),
Expand All @@ -95,14 +99,14 @@ func TestFormatCelTypeEquivalence(t *testing.T) {
for _, v := range values {
v := v
t.Run(v.String(), func(t *testing.T) {
celStr := FormatCelType(v)
celStr := FormatCELType(v)
et, err := TypeToExprType(v)
if err != nil {
t.Fatalf("TypeToExprType(%v) failed: %v", v, err)
}
exprStr := FormatType(et)
if celStr != exprStr {
t.Errorf("FormatCelType(%v) got %s, wanted %s", v, celStr, exprStr)
t.Errorf("FormatCELType(%v) got %s, wanted %s", v, celStr, exprStr)
}
})
}
Expand Down Expand Up @@ -133,6 +137,89 @@ func TestEnvCheckExtendRace(t *testing.T) {
}
}

func TestTypeProviderInterop(t *testing.T) {
reg, err := types.NewRegistry(&proto3pb.TestAllTypes{})
if err != nil {
t.Fatalf("types.NewRegistry() failed: %v", err)
}
tests := []struct {
name string
provider any
}{
{
name: "custom provider",
provider: &customCELProvider{provider: reg},
},
{
name: "custom legacy provider",
provider: &customLegacyProvider{provider: reg},
},
{
name: "provider",
provider: reg,
},
}
for _, tst := range tests {
tc := tst
t.Run(tc.name, func(t *testing.T) {
env, err := NewEnv(CustomTypeProvider(tc.provider))
if err != nil {
t.Fatalf("NewEnv(CustomTypeProvider()) failed: %v", err)
}
// Found type
pbType, found := env.TypeProvider().FindType("google.expr.proto3.test.TestAllTypes")
if !found {
t.Fatal("FindType(google.expr.proto3.test.TestAllTypes) failed")
}
celType, found := env.CELTypeProvider().FindStructType("google.expr.proto3.test.TestAllTypes")
if !found {
t.Fatal("FindStructType(google.expr.proto3.test.TestAllTypes) failed")
}
pbConvType, err := types.ExprTypeToType(pbType)
if err != nil {
t.Fatalf("types.ExprTypeToType(%v) failed: %v", pbType, err)
}
if !celType.IsExactType(pbConvType) {
t.Errorf("got converted type %v, wanted %v", pbConvType, celType)
}
// Found field
pbFieldType, found := env.TypeProvider().FindFieldType("google.expr.proto3.test.TestAllTypes", "single_int32")
if !found {
t.Fatal("FindFieldType(google.expr.proto3.test.TestAllTypes, single_int32) not found")
}
celFieldType, found := env.CELTypeProvider().FindStructFieldType("google.expr.proto3.test.TestAllTypes", "single_int32")
if !found {
t.Fatal("FindStructFieldType(google.expr.proto3.test.TestAllTypes, single_int32) not found")
}
pbConvFieldType, err := types.ExprTypeToType(pbFieldType.Type)
if err != nil {
t.Fatalf("types.ExprTypeToType(%v) failed: %v", pbFieldType.Type, err)
}
if !celFieldType.Type.IsExactType(pbConvFieldType) {
t.Errorf("got converted field type %v, wanted %v", pbConvFieldType, celFieldType)
}
// Not found type
_, found = env.TypeProvider().FindType("test.BadTypeName")
if found {
t.Fatal("FindType(test.BadTypeName) succeeded")
}
_, found = env.CELTypeProvider().FindStructType("test.BadTypeName")
if found {
t.Fatal("FindStructType(test.BadTypeName) succeeded")
}
// Not found field
_, found = env.TypeProvider().FindFieldType("google.expr.proto3.test.TestAllTypes", "undefined_field")
if found {
t.Fatal("FindFieldType(google.expr.proto3.test.TestAllTypes, undefined_field) was found")
}
_, found = env.CELTypeProvider().FindStructFieldType("google.expr.proto3.test.TestAllTypes", "undefined_field")
if found {
t.Fatal("FindStructFieldType(google.expr.proto3.test.TestAllTypes, undefined_field) not found")
}
})
}
}

func BenchmarkNewCustomEnvLazy(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand Down Expand Up @@ -230,3 +317,51 @@ func BenchmarkEnvExtendEagerDecls(b *testing.B) {
}
}
}

type customLegacyProvider struct {
provider ref.TypeProvider
}

func (p *customLegacyProvider) EnumValue(enumName string) ref.Val {
return p.provider.EnumValue(enumName)
}

func (p *customLegacyProvider) FindIdent(identName string) (ref.Val, bool) {
return p.provider.FindIdent(identName)
}

func (p *customLegacyProvider) FindType(typeName string) (*exprpb.Type, bool) {
return p.provider.FindType(typeName)
}

func (p *customLegacyProvider) FindFieldType(structType, fieldName string) (*ref.FieldType, bool) {
return p.provider.FindFieldType(structType, fieldName)
}

func (p *customLegacyProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val {
return p.provider.NewValue(structType, fields)
}

type customCELProvider struct {
provider types.Provider
}

func (p *customCELProvider) EnumValue(enumName string) ref.Val {
return p.provider.EnumValue(enumName)
}

func (p *customCELProvider) FindIdent(identName string) (ref.Val, bool) {
return p.provider.FindIdent(identName)
}

func (p *customCELProvider) FindStructType(typeName string) (*types.Type, bool) {
return p.provider.FindStructType(typeName)
}

func (p *customCELProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) {
return p.provider.FindStructFieldType(structType, fieldName)
}

func (p *customCELProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val {
return p.provider.NewValue(structType, fields)
}
2 changes: 1 addition & 1 deletion cel/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ var (
)

// ValueToRefValue converts between exprpb.Value and ref.Val.
func ValueToRefValue(adapter ref.TypeAdapter, v *exprpb.Value) (ref.Val, error) {
func ValueToRefValue(adapter types.Adapter, v *exprpb.Value) (ref.Val, error) {
switch v.Kind.(type) {
case *exprpb.Value_NullValue:
return types.NullValue, nil
Expand Down
2 changes: 1 addition & 1 deletion cel/io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestRefValueToValueRoundTrip(t *testing.T) {
{value: types.Duration{Duration: time.Hour}},
{value: types.Timestamp{Time: time.Unix(0, 0)}},
{value: types.IntType},
{value: types.NewTypeValue("CustomType")},
{value: types.NewOpaqueType("CustomType")},
{value: map[int64]int64{1: 1}},
{value: []any{true, "abc"}},
{value: &proto3pb.TestAllTypes{SingleString: "abc"}},
Expand Down
Loading

0 comments on commit 57a22de

Please sign in to comment.