Motivation
Program.references() answers "which names does this expression use", but several things people ask for need the structure of the expression, not just its leaves:
What upstream gives us
cel::Program::expression() returns the parsed Expression (IdedExpr: an id plus an Expr enum covering literals, identifiers, selects, calls, lists, maps, structs and the expanded comprehensions). No protobuf AST, but the shape is close to cel.expr.Expr and would be straightforward to mirror as nested dicts or small dataclasses.
Proposal
Program.ast() returning a plain, JSON-serialisable tree, e.g. {"kind": "call", "function": "_&&_", "args": [...]}, with comprehension macros shown in their expanded form (that is what cel-rust holds after parsing; the original macro call is not retained). Ship it as provisional, tied to the cel-rust version, and note that the node shapes may change when cel-rust changes its AST.
A Python-side cel.explain(program, context) built on it could then evaluate each boolean sub-expression and report the first failing clause, which covers the exists/all case in #30 without upstream changes, at the cost of re-evaluation (custom functions run again).
Motivation
Program.references()answers "which names does this expression use", but several things people ask for need the structure of the expression, not just its leaves:exists/&&chain true or false. cel-rust has no evaluation hooks, so a wrapper-sideexplain()would have to walk the tree and re-evaluate sub-expressions.matches()on untrusted input", "no more than N nested comprehensions", "field access only underrequest.".What upstream gives us
cel::Program::expression()returns the parsedExpression(IdedExpr: an id plus anExprenum covering literals, identifiers, selects, calls, lists, maps, structs and the expanded comprehensions). No protobuf AST, but the shape is close tocel.expr.Exprand would be straightforward to mirror as nested dicts or small dataclasses.Proposal
Program.ast()returning a plain, JSON-serialisable tree, e.g.{"kind": "call", "function": "_&&_", "args": [...]}, with comprehension macros shown in their expanded form (that is what cel-rust holds after parsing; the original macro call is not retained). Ship it as provisional, tied to the cel-rust version, and note that the node shapes may change when cel-rust changes its AST.A Python-side
cel.explain(program, context)built on it could then evaluate each boolean sub-expression and report the first failing clause, which covers theexists/allcase in #30 without upstream changes, at the cost of re-evaluation (custom functions run again).