cel: Leverage DefaultAdapter to extend CEL's type system

Thanks to @TristonianJones for the tip!
105acfa086 (r38358983)
This commit is contained in:
Matthew Holt 2020-04-08 10:44:36 -06:00
parent e30deedcc1
commit 4d9b63d909
1 changed files with 37 additions and 61 deletions

View File

@ -53,6 +53,7 @@ type MatchExpression struct {
expandedExpr string
prg cel.Program
ta ref.TypeAdapter
}
// CaddyModule returns the Caddy module information.
@ -79,6 +80,9 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
// light (and possibly naïve) syntactic sugar
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
// our type adapter expands CEL's standard type support
m.ta = celTypeAdapter{}
// create the CEL environment
env, err := cel.NewEnv(
cel.Declarations(
@ -88,7 +92,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
[]*exprpb.Type{httpRequestObjectType, decls.String},
decls.Any)),
),
cel.CustomTypeAdapter(celHTTPRequestTypeAdapter{}),
cel.CustomTypeAdapter(m.ta),
ext.Strings(),
)
if err != nil {
@ -112,7 +116,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
cel.Functions(
&functions.Overload{
Operator: placeholderFuncName,
Binary: caddyPlaceholderFunc,
Binary: m.caddyPlaceholderFunc,
},
),
)
@ -143,14 +147,34 @@ func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
// caddyPlaceholderFunc implements the custom CEL function that accesses the
// Replacer on a request and gets values from it.
func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
celReq, ok := lhs.(celHTTPRequest)
if !ok {
return types.NewErr(
"invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
lhs.Type())
}
phStr, ok := rhs.(types.String)
if !ok {
return types.NewErr(
"invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
rhs.Type())
}
repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
val, _ := repl.Get(string(phStr))
return m.ta.NativeToValue(val)
}
// httpRequestCELType is the type representation of a native HTTP request.
var httpRequestCELType = types.NewTypeValue("http.Request", traits.ReceiverType)
// cellHTTPRequest wraps an http.Request with
// methods to satisfy the ref.Val interface.
type celHTTPRequest struct {
*http.Request
}
type celHTTPRequest struct{ *http.Request }
func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
return cr.Request, nil
@ -167,13 +191,15 @@ func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
func (cr celHTTPRequest) Value() interface{} { return cr }
// celHTTPRequestTypeAdapter can adapt a
// celHTTPRequest to a CEL value.
type celHTTPRequestTypeAdapter struct{}
// celTypeAdapter can adapt our custom types to a CEL value.
type celTypeAdapter struct{}
func (celHTTPRequestTypeAdapter) NativeToValue(value interface{}) ref.Val {
if celReq, ok := value.(celHTTPRequest); ok {
return celReq
func (celTypeAdapter) NativeToValue(value interface{}) ref.Val {
switch v := value.(type) {
case celHTTPRequest:
return v
case error:
types.NewErr(v.Error())
}
return types.DefaultTypeAdapter.NativeToValue(value)
}
@ -191,56 +217,6 @@ var httpRequestObjectType = decls.NewObjectType("http.Request")
// The name of the CEL function which accesses Replacer values.
const placeholderFuncName = "caddyPlaceholder"
// caddyPlaceholderFunc implements the custom CEL function that
// accesses the Replacer on a request and gets values from it.
func caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
celReq, ok := lhs.(celHTTPRequest)
if !ok {
return types.NewErr(
"invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
lhs.Type())
}
phStr, ok := rhs.(types.String)
if !ok {
return types.NewErr(
"invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
rhs.Type())
}
repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
val, _ := repl.Get(string(phStr))
// TODO: this is... kinda awful and underwhelming, how can we expand CEL's type system more easily?
switch v := val.(type) {
case string:
return types.String(v)
case fmt.Stringer:
return types.String(v.String())
case error:
return types.NewErr(v.Error())
case int:
return types.Int(v)
case int32:
return types.Int(v)
case int64:
return types.Int(v)
case uint:
return types.Int(v)
case uint32:
return types.Int(v)
case uint64:
return types.Int(v)
case float32:
return types.Double(v)
case float64:
return types.Double(v)
case bool:
return types.Bool(v)
default:
return types.String(fmt.Sprintf("%+v", v))
}
}
// Interface guards
var (
_ caddy.Provisioner = (*MatchExpression)(nil)