Skip to content

Commit

Permalink
Update the ConvertToNative API documentation (#692)
Browse files Browse the repository at this point in the history
  • Loading branch information
TristonianJones committed Apr 29, 2023
1 parent 43bdc4a commit d651ee2
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 34 deletions.
11 changes: 10 additions & 1 deletion common/types/ref/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,18 @@ type Type interface {
type Val interface {
// ConvertToNative converts the Value to a native Go struct according to the
// reflected type description, or error if the conversion is not feasible.
//
// The ConvertToNative method is intended to be used to support conversion between CEL types
// and native types during object creation expressions or by clients who need to adapt the,
// returned CEL value into an equivalent Go value instance.
//
// When implementing or using ConvertToNative, the following guidelines apply:
// - Use ConvertToNative when marshalling CEL evaluation results to native types.
// - Do not use ConvertToNative within CEL extension functions.
// - Document whether your implementation supports non-CEL field types, such as Go or Protobuf.
ConvertToNative(typeDesc reflect.Type) (any, error)

// ConvertToType supports type conversions between value types supported by the expression language.
// ConvertToType supports type conversions between CEL value types supported by the expression language.
ConvertToType(typeValue Type) Val

// Equal returns true if the `other` value has the same type and content as the implementing struct.
Expand Down
76 changes: 56 additions & 20 deletions ext/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const (
// `%X` - same as above, but with A-F capitalized.
// `%o` - substitutes an integer with its equivalent in octal.
//
// <string>.format(<list>)` -> <string>
// <string>.format(<list>) -> <string>
//
// Examples:
//
Expand Down Expand Up @@ -431,30 +431,12 @@ func (sl *stringLib) CompileOptions() []cel.EnvOption {
s := str.(types.String)
return stringOrError(upperASCII(string(s)))
}))),
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
return stringOrError(join(l.([]string)))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
d := delim.(types.String)
return stringOrError(joinSeparator(l.([]string), string(d)))
}))),
}
if sl.version >= 1 {
opts = append(opts, cel.Function("format",
cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
s := args[0].(types.String).Value().(string)
s := string(args[0].(types.String))
formatArgs := args[1].(traits.Lister)
return stringOrError(interpreter.ParseFormatString(s, &stringFormatter{}, &stringArgList{formatArgs}, formatLocale))
}))),
Expand All @@ -465,6 +447,43 @@ func (sl *stringLib) CompileOptions() []cel.EnvOption {
}))))

}
if sl.version >= 2 {
opts = append(opts,
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l := list.(traits.Lister)
return stringOrError(joinValSeparator(l, ""))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l := list.(traits.Lister)
d := delim.(types.String)
return stringOrError(joinValSeparator(l, string(d)))
}))),
)
} else {
opts = append(opts,
cel.Function("join",
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
cel.UnaryBinding(func(list ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
return stringOrError(join(l.([]string)))
})),
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
l, err := list.ConvertToNative(stringListType)
if err != nil {
return types.NewErr(err.Error())
}
d := delim.(types.String)
return stringOrError(joinSeparator(l.([]string), string(d)))
}))),
)
}
return opts
}

Expand Down Expand Up @@ -623,6 +642,23 @@ func join(strs []string) (string, error) {
return strings.Join(strs, ""), nil
}

func joinValSeparator(strs traits.Lister, separator string) (string, error) {
sz := strs.Size().(types.Int)
var sb strings.Builder
for i := types.Int(0); i < sz; i++ {
if i != 0 {
sb.WriteString(separator)
}
elem := strs.Get(i)
str, ok := elem.(types.String)
if !ok {
return "", fmt.Errorf("join: invalid input: %v", elem)
}
sb.WriteString(string(str))
}
return sb.String(), nil
}

type clauseImpl func(ref.Val, string) (string, error)

func clauseForType(argType ref.Type) (clauseImpl, error) {
Expand Down
39 changes: 26 additions & 13 deletions ext/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,23 +357,24 @@ func TestVersions(t *testing.T) {
{
version: 0,
supportedFunctions: map[string]string{
"chatAt": "''.charAt(0)",
"indexOf": "'a'.indexOf('a')",
"lastIndexOf": "'a'.lastIndexOf('a')",
"join": "['a', 'b'].join()",
"lowerAscii": "'a'.lowerAscii()",
"replace": "'hello hello'.replace('he', 'we')",
"split": "'hello hello hello'.split(' ')",
"substring": "'tacocat'.substring(4)",
"trim": "' \\ttrim\\n '.trim()",
"upperAscii": "'TacoCat'.upperAscii()",
"chatAt": "''.charAt(0) == ''",
"indexOf": "'a'.indexOf('a') == 0",
"lastIndexOf": "'a'.lastIndexOf('a') == 0",
"join": "['a', 'b'].join() == 'ab'",
"joinSep": "['a', 'b'].join('-') == 'a-b'",
"lowerAscii": "'a'.lowerAscii() == 'a'",
"replace": "'hello hello'.replace('he', 'we') == 'wello wello'",
"split": "'hello hello hello'.split(' ') == ['hello', 'hello', 'hello']",
"substring": "'tacocat'.substring(4) == 'cat'",
"trim": "' \\ttrim\\n '.trim() == 'trim'",
"upperAscii": "'TacoCat'.upperAscii() == 'TACOCAT'",
},
},
{
version: 1,
supportedFunctions: map[string]string{
"format": "'a %d'.format([1])",
"quote": `strings.quote('\a \b "double quotes"')`,
"format": "'a %d'.format([1]) == 'a 1'",
"quote": `strings.quote('\a \b "double quotes"') == '"\\a \\b \\"double quotes\\""'`,
},
},
}
Expand All @@ -387,7 +388,7 @@ func TestVersions(t *testing.T) {
for name, expr := range tc.supportedFunctions {
supported := lib.version >= tc.version
t.Run(fmt.Sprintf("%s-supported=%t", name, supported), func(t *testing.T) {
_, iss := env.Compile(expr)
ast, iss := env.Compile(expr)
if supported {
if iss.Err() != nil {
t.Errorf("unexpected error: %v", iss.Err())
Expand All @@ -396,6 +397,18 @@ func TestVersions(t *testing.T) {
if iss.Err() == nil || !strings.Contains(iss.Err().Error(), "undeclared reference") {
t.Errorf("got error %v, wanted error %s for expr: %s, version: %d", iss.Err(), "undeclared reference", expr, tc.version)
}
return
}
prg, err := env.Program(ast)
if err != nil {
t.Fatalf("env.Program() failed: %v", err)
}
out, _, err := prg.Eval(cel.NoVars())
if err != nil {
t.Fatalf("prg.Eval() failed: %v", err)
}
if out != types.True {
t.Errorf("prg.Eval() got %v, wanted true", out)
}
})
}
Expand Down

0 comments on commit d651ee2

Please sign in to comment.