Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
48 changes: 29 additions & 19 deletions cel/decls.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,32 @@ func MemberOverload(overloadID string, args []*Type, resultType *Type, opts ...O
return decls.MemberOverload(overloadID, args, resultType, opts...)
}

// FunctionDeclOverloadToFunctionOpt converts a list of protobuf CEL FunctionDecl_Overload instances
// to a list of FunctionOpt instances.
func FunctionDeclOverloadToFunctionOpt(overloads []*celpb.Decl_FunctionDecl_Overload) ([]FunctionOpt, error) {
opts := make([]FunctionOpt, len(overloads))
for i, o := range overloads {
args := make([]*Type, len(o.GetParams()))
for j, p := range o.GetParams() {
a, err := types.ProtoAsType(p)
if err != nil {
return nil, err
}
args[j] = a
}
res, err := types.ProtoAsType(o.GetResultType())
if err != nil {
return nil, err
}
if o.IsInstanceFunction {
opts[i] = decls.MemberOverload(o.GetOverloadId(), args, res)
} else {
opts[i] = decls.Overload(o.GetOverloadId(), args, res)
}
}
return opts, nil
}

// OverloadOpt is a functional option for configuring a function overload.
type OverloadOpt = decls.OverloadOpt

Expand Down Expand Up @@ -372,25 +398,9 @@ func ProtoAsDeclaration(d *celpb.Decl) (EnvOption, error) {
switch d.GetDeclKind().(type) {
case *celpb.Decl_Function:
overloads := d.GetFunction().GetOverloads()
opts := make([]FunctionOpt, len(overloads))
for i, o := range overloads {
args := make([]*Type, len(o.GetParams()))
for j, p := range o.GetParams() {
a, err := types.ProtoAsType(p)
if err != nil {
return nil, err
}
args[j] = a
}
res, err := types.ProtoAsType(o.GetResultType())
if err != nil {
return nil, err
}
if o.IsInstanceFunction {
opts[i] = decls.MemberOverload(o.GetOverloadId(), args, res)
} else {
opts[i] = decls.Overload(o.GetOverloadId(), args, res)
}
opts, err := FunctionDeclOverloadToFunctionOpt(overloads)
if err != nil {
return nil, err
}
return Function(d.GetName(), opts...), nil
case *celpb.Decl_Ident:
Expand Down
15 changes: 15 additions & 0 deletions common/ast/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,21 @@ func ProtoConstantAsVal(c *celpb.Constant) (ref.Val, error) {
return nil, fmt.Errorf("unsupported constant kind: %v", c.GetConstantKind())
}

// ProtoStructValueAsVal converts a canonical structpb.Value protobuf to a CEL-native ref.Val.
func ProtoStructValueAsVal(c *structpb.Value) (ref.Val, error) {
switch c.GetKind().(type) {
case *structpb.Value_NullValue:
return types.NullValue, nil
case *structpb.Value_NumberValue:
return types.Double(c.GetNumberValue()), nil
case *structpb.Value_StringValue:
return types.String(c.GetStringValue()), nil
case *structpb.Value_BoolValue:
return types.Bool(c.GetBoolValue()), nil
}
return nil, fmt.Errorf("unsupported value kind: %v", c.GetKind())
}

func convertProto(src, dst proto.Message) error {
pb, err := proto.Marshal(src)
if err != nil {
Expand Down
38 changes: 23 additions & 15 deletions common/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,25 +141,33 @@ func (c *Config) AddFunctionDecls(funcs ...*decls.FunctionDecl) *Config {
if fn == nil {
continue
}
overloads := make([]*Overload, 0, len(fn.OverloadDecls()))
for _, o := range fn.OverloadDecls() {
overloadID := o.ID()
args := make([]*TypeDesc, 0, len(o.ArgTypes()))
for _, a := range o.ArgTypes() {
args = append(args, serializeTypeDesc(a))
}
ret := serializeTypeDesc(o.ResultType())
if o.IsMemberFunction() {
overloads = append(overloads, NewMemberOverload(overloadID, args[0], args[1:], ret))
} else {
overloads = append(overloads, NewOverload(overloadID, args, ret))
}
}
convFuncs[i] = NewFunction(fn.Name(), overloads...)
convFuncs[i] = FunctionDeclToFunction(fn)
}
return c.AddFunctions(convFuncs...)
}

// FunctionDeclToFunction converts a protobuf CEL FunctionDecl to a serializable Function definition
// which can be added to the config.
//
// FunctionDecl inputs are expected to be well-formed.
func FunctionDeclToFunction(fn *decls.FunctionDecl) *Function {
overloads := make([]*Overload, 0, len(fn.OverloadDecls()))
for _, o := range fn.OverloadDecls() {
overloadID := o.ID()
args := make([]*TypeDesc, 0, len(o.ArgTypes()))
for _, a := range o.ArgTypes() {
args = append(args, serializeTypeDesc(a))
}
ret := serializeTypeDesc(o.ResultType())
if o.IsMemberFunction() {
overloads = append(overloads, NewMemberOverload(overloadID, args[0], args[1:], ret))
} else {
overloads = append(overloads, NewOverload(overloadID, args, ret))
}
}
return NewFunction(fn.Name(), overloads...)
}

// AddFunctions adds one or more functions to the config.
func (c *Config) AddFunctions(funcs ...*Function) *Config {
c.Functions = append(c.Functions, funcs...)
Expand Down
72 changes: 72 additions & 0 deletions ext/extension_option_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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 ext

import (
"fmt"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/env"
)

// ExtensionOptionFactory converts an ExtensionConfig value to a CEL environment option.
func ExtensionOptionFactory(configElement any) (cel.EnvOption, bool) {
ext, isExtension := configElement.(*env.Extension)
if !isExtension {
return nil, false
}
fac, found := extFactories[ext.Name]
if !found {
return nil, false
}
// If the version is 'latest', set the version value to the max uint.
ver, err := ext.VersionNumber()
if err != nil {
return func(*cel.Env) (*cel.Env, error) {
return nil, fmt.Errorf("invalid extension version: %s - %s", ext.Name, ext.Version)
}, true
}
return fac(ver), true
}

// extensionFactory accepts a version and produces a CEL environment associated with the versioned extension.
type extensionFactory func(uint32) cel.EnvOption

var extFactories = map[string]extensionFactory{
"bindings": func(version uint32) cel.EnvOption {
return Bindings(BindingsVersion(version))
},
"encoders": func(version uint32) cel.EnvOption {
return Encoders(EncodersVersion(version))
},
"lists": func(version uint32) cel.EnvOption {
return Lists(ListsVersion(version))
},
"math": func(version uint32) cel.EnvOption {
return Math(MathVersion(version))
},
"protos": func(version uint32) cel.EnvOption {
return Protos(ProtosVersion(version))
},
"sets": func(version uint32) cel.EnvOption {
return Sets(SetsVersion(version))
},
"strings": func(version uint32) cel.EnvOption {
return Strings(StringsVersion(version))
},
"two-var-comprehensions": func(version uint32) cel.EnvOption {
return TwoVarComprehensions(TwoVarComprehensionsVersion(version))
},
}
54 changes: 1 addition & 53 deletions policy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package policy

import (
"fmt"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/env"
"github.com/google/cel-go/ext"
Expand All @@ -28,55 +26,5 @@ import (
// a set of configuration ConfigOptionFactory values to handle extensions and other config features
// which may be defined outside of the `cel` package.
func FromConfig(config *env.Config) cel.EnvOption {
return cel.FromConfig(config, extensionOptionFactory)
}

// extensionOptionFactory converts an ExtensionConfig value to a CEL environment option.
func extensionOptionFactory(configElement any) (cel.EnvOption, bool) {
ext, isExtension := configElement.(*env.Extension)
if !isExtension {
return nil, false
}
fac, found := extFactories[ext.Name]
if !found {
return nil, false
}
// If the version is 'latest', set the version value to the max uint.
ver, err := ext.VersionNumber()
if err != nil {
return func(*cel.Env) (*cel.Env, error) {
return nil, fmt.Errorf("invalid extension version: %s - %s", ext.Name, ext.Version)
}, true
}
return fac(ver), true
}

// extensionFactory accepts a version and produces a CEL environment associated with the versioned extension.
type extensionFactory func(uint32) cel.EnvOption

var extFactories = map[string]extensionFactory{
"bindings": func(version uint32) cel.EnvOption {
return ext.Bindings(ext.BindingsVersion(version))
},
"encoders": func(version uint32) cel.EnvOption {
return ext.Encoders(ext.EncodersVersion(version))
},
"lists": func(version uint32) cel.EnvOption {
return ext.Lists(ext.ListsVersion(version))
},
"math": func(version uint32) cel.EnvOption {
return ext.Math(ext.MathVersion(version))
},
"protos": func(version uint32) cel.EnvOption {
return ext.Protos(ext.ProtosVersion(version))
},
"sets": func(version uint32) cel.EnvOption {
return ext.Sets(ext.SetsVersion(version))
},
"strings": func(version uint32) cel.EnvOption {
return ext.Strings(ext.StringsVersion(version))
},
"two-var-comprehensions": func(version uint32) cel.EnvOption {
return ext.TwoVarComprehensions(ext.TwoVarComprehensionsVersion(version))
},
return cel.FromConfig(config, ext.ExtensionOptionFactory)
}