-
Notifications
You must be signed in to change notification settings - Fork 21
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
Make Functions Send + Sync #55
Conversation
Hm, what's an example of something that doesn't work without this PR? In this example, is the goal to share a |
Why would you want to bind
In the context of an Axum server, let's say that you were implementing some sort of auth validation or something with CEL. The program could be shared across Axum handlers (this would be the CEL logic for validating), but on each Axum request, you'd create a new Context and add variables like the request path, the client IP, etc which could then be reference in the thread-safe program. This is sort of the mental model that I have for the difference between a Can you elaborate more on how your use-case differs from the design here? |
Basically I want to be able to make authorization decisions for HTTP calls. Policy conditions, expressed as CEL, are supplied by admins and stored somewhere (database, in-memory, etc.). When users want to do something, a Now, I'm not wedded to this model and if you think there's a better way to do things given those goals, I'm all ears. The only alternative that really comes to mind is building a brand new context for every request, which I would prefer not to do. |
@lucperkins thanks for the explanation of the use-case. It makes perfect sense. In addition to making the functions You know what could be interesting... the context can either be a root context, or a child context (see blog post about it) where the child context just has a reference to it's parent which could be another child context, or it could be the root context. This is how we support variable shadowing in CEL macros like .map. When a variable is not found in a child context, we traverse upwards until we get to the root. We could look at implementing copy-on-write semantics in order to make Context The only other question is whether we'd run into lifetime issues where the compiler won't be able to prove that the root context outlives the child contexts passed to the Axum handlers. Anyways, that's sort of a brain-dump. Thoughts? |
@clarkmcc First off, thanks a ton in turn for entertaining my suggestion and thinking it through so thoroughly. @cole-h has a maybe-solution to this where he has a struct that wraps struct PolicyDecider(Context<'static>);
impl PolicyDecider {
fn new() -> Self { Self(Context::default()) }
fn evaluate(&self, condition: &str) -> Result<Value, ExecutionError> {
let mut inner_context = self.0.new_inner_scope();
inner_context.add_variable_from_value("someVar", Value::from("someVal"));
let program = Program::compile(condition)?;
program.execute(inner_context)
}
} So basically using the mechanism that you're currently using for |
@lucperkins yes, this is exactly what I had in mind. If this meets your needs, I think I'd also like to clean up the API a bit so that it's more obvious what functions to call to do exactly this. I'm also trying to think through whether we can just make this implicit behind the scenes -- i.e. context is always immutable and when you add a variable, it creates a child context on the spot and gives it back. I would need to figure out the best way to do that with support for batching variables into the context, and I would also need to figure out if doing this implicitly presents lifetime challenges for the user. |
@clarkmcc I'd say this indeed meets our needs for now. More than happy to help out re: API design, so please don't hesitate to ping us. I'm closing this specific PR for now as I definitely agree about |
@clarkmcc I take it back. I conferred with Cole again, and actually we want to re-propose this specific change. Even when carefully handling mutability as in the pattern I laid out above and using |
Would this PR be sufficient though? Context is still not |
And if I could throw out something else for consideration: there's probably a good argument for registering functions with a If we were to move functions to the |
@clarkmcc In our case, it's okay for The issue with your suggestion, I think, is that we don't know what the |
@lucperkins no big deal, but if you have a spare moment, it would be great if you could add a minimum reproducible example of your use-case to the examples directory. Since we're pre 1.0, I'd like to capture as many use-cases as possible so that any breaking changes going forward still support the legitimate use-cases that exist in the wild. If you don't have the time, not a big deal at all. |
@clarkmcc More than happy to! |
This makes implementations of
Function
usable in multi-threaded contexts.I'm happy to put this behind a feature flag if desired.