mirror of https://github.com/caddyserver/caddy.git
cel: Leverage DefaultAdapter to extend CEL's type system
Thanks to @TristonianJones for the tip!
105acfa086 (r38358983)
This commit is contained in:
parent
e30deedcc1
commit
4d9b63d909
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue