Skip to main content

ferrin_policy/
client.rs

1//! The policy client abstraction.
2
3use std::fmt;
4use std::sync::Arc;
5
6use ferrin_spec::BoxFuture;
7use ferrin_spec::JsonValue;
8
9use crate::error::PolicyError;
10
11/// Evaluates policies: a path and a JSON input in, a raw decision document
12/// out.
13///
14/// The raw document is normalized by
15/// [`PolicyDecision::normalize`](crate::PolicyDecision::normalize) (approval)
16/// or [`parse_allowlist`](crate::parse_allowlist) (capabilities). Clients
17/// return `JsonValue::Null` when the policy produced no value (an undefined
18/// rule), never an error.
19pub trait PolicyClient: Send + Sync + 'static {
20    /// Evaluates the policy at `path` with `input`.
21    fn evaluate<'a>(
22        &'a self,
23        path: &'a str,
24        input: JsonValue,
25    ) -> BoxFuture<'a, Result<JsonValue, PolicyError>>;
26}
27
28impl<T: PolicyClient + ?Sized> PolicyClient for Arc<T> {
29    fn evaluate<'a>(
30        &'a self,
31        path: &'a str,
32        input: JsonValue,
33    ) -> BoxFuture<'a, Result<JsonValue, PolicyError>> {
34        (**self).evaluate(path, input)
35    }
36}
37
38/// Shared reference to a policy client.
39pub type SharedPolicyClient = Arc<dyn PolicyClient>;
40
41/// Adapter turning a synchronous closure into a [`PolicyClient`].
42pub struct PolicyClientFn<F>(F);
43
44/// Wraps a synchronous closure `(path, input) -> decision` as a policy
45/// client, for tests and static rules.
46pub fn policy_client<F>(f: F) -> PolicyClientFn<F>
47where
48    F: Fn(&str, JsonValue) -> Result<JsonValue, PolicyError> + Send + Sync + 'static,
49{
50    PolicyClientFn(f)
51}
52
53impl<F> fmt::Debug for PolicyClientFn<F> {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.write_str("PolicyClientFn(..)")
56    }
57}
58
59impl<F> PolicyClient for PolicyClientFn<F>
60where
61    F: Fn(&str, JsonValue) -> Result<JsonValue, PolicyError> + Send + Sync + 'static,
62{
63    fn evaluate<'a>(
64        &'a self,
65        path: &'a str,
66        input: JsonValue,
67    ) -> BoxFuture<'a, Result<JsonValue, PolicyError>> {
68        let result = (self.0)(path, input);
69        Box::pin(async move { result })
70    }
71}