Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Evaluate JSON Pointers in Policy Expressions #418

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

cstepanian
Copy link
Contributor

@cstepanian cstepanian commented Sep 17, 2024

Relates to #371

Description

This PR replaces the previous preprocessor-based implementation of JSON Pointers with one that natively runs in the Policy Expression Executor.

It replaces the context parameter on run() and parse_and_eval() with a new field on the Env struct. Now, calling code must pass the JSON context while creating the Env.

It also fixes a bug that the previous version had but which was hidden by the intermediate step of serializing to text: numbers in the JSON that could be represented as integers were being forced to f64. This is a problem for Policy Expressions, which distinguishes between Ints and Floats and does not currently have any casting or conversion between them. Since the previous version went through Rust's native printing of f64, and that printing code will display a float without a decimal point if it is exactly equal to an integer, the bug did not manifest.

This version goes directly from the Expr returned by json_to_policy_expr() to the Executor, so it includes a fix to return Primitive::Int values where appropriate.

Feedback requested

  • Should the Env struct contain the JSON context data? I considered an approach where the context value is passed around separately from the Env, but it would get messy quickly due to all the operators needing to pass it around. It seems mostly natural to me to put in the Env, except that the context is effectively a global variable. It doesn't interact with the lexical scoping (should it?), so this PR currently just clones the JSON when a child Env is created.
  • Assuming yes to the previous question: Should the Env struct own or borrow the context? This version has an owned copy for simplicity. The only issue I see with borrowing it is needing to add another lifetime parameter to Env, which is manageable.
  • If Env should own a copy of context: should Env::child() pass a reference instead of a clone? I considered using Rc<Box<Value>> to keep one shared version of the context, but I wasn't sure if it was worth the trouble.
  • Is it the right approach to add the context parameter to Executor::std()? Should the Executor be built or used in a different way?

This commit replaces the previous preprocessor-based implementation of
JSON Pointers with one that natively runs in the Policy Expression
`Executor`.

It replaces the `context` parameter on `run()` and `parse_and_eval()`
with a new field on the `Env` struct. Now, calling code must pass the
JSON context while creating the `Env`.

It also fixes a bug that the previous version had but which was hidden
by the intermediate step of serializing to text: numbers in the JSON
that could be represented as integers were being forced to f64. This is
a problem for Policy Expressions, which distinguishes between Ints and
Floats and does not currently have any casting or conversion between
them. Since the previous version went through Rust's native printing of
f64, and that printing code will display a float without a decimal point
if it is exactly equal to an integer, the bug did not manifest.

This version goes directly from the Expr returned by
`json_to_policy_expr()` to the Executor, so it includes a fix to return
Primitive::Int values where appropriate.
@j-lanson j-lanson added product: hc Relates to the core "hc" binary type: enhancement New feature or request labels Sep 18, 2024
@j-lanson
Copy link
Collaborator

j-lanson commented Sep 18, 2024

Hey Cal,

Your solution right now is very reasonable, and I don't mind merging it as-is and making changes to it in a subsequent PR.

As for your questions about passing around the context, let me know if this is a possible solution.

The way I was imagining it, you have an Expr that represents the parsed program, which may contain JsonPointer variants. You have a function preprocess(expr: &mut Expr, context: &Value) which produces an Expr tree guaranteed to have no JsonPointer variants. You can then execute the program as normal without having to pass around &context.

It's like the JSON pointers are macros, but they get replaced once we have a program AST instead of before everything is tokenized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product: hc Relates to the core "hc" binary type: enhancement New feature or request
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

2 participants