From 4d9b63d9097bd7bac46ecc06c292bf7c521fdcb6 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 8 Apr 2020 10:44:36 -0600 Subject: cel: Leverage DefaultAdapter to extend CEL's type system Thanks to @TristonianJones for the tip! https://github.com/caddyserver/caddy/commit/105acfa08664c97460a6fe3fb49635618be5bcb2#r38358983 --- modules/caddyhttp/celmatcher.go | 98 ++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 61 deletions(-) (limited to 'modules/caddyhttp/celmatcher.go') diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index 84565e4..ddaf418 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -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) -- cgit v1.2.3