1use std::fmt;
4
5use ferrin_spec::BoxFuture;
6use ferrin_spec::JsonValue;
7use regorus::Engine;
8use regorus::Value;
9
10use crate::client::PolicyClient;
11use crate::error::PolicyError;
12use crate::path::PolicyPath;
13
14pub struct RegoPolicyClient {
23 engine: Engine,
24 packages: Vec<String>,
25}
26
27impl fmt::Debug for RegoPolicyClient {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.debug_struct("RegoPolicyClient")
30 .field("packages", &self.packages)
31 .finish_non_exhaustive()
32 }
33}
34
35impl RegoPolicyClient {
36 #[must_use]
38 pub fn builder() -> RegoPolicyClientBuilder {
39 RegoPolicyClientBuilder::default()
40 }
41
42 pub fn from_policy(
48 name: impl Into<String>,
49 source: impl Into<String>,
50 ) -> Result<Self, PolicyError> {
51 Self::builder().policy(name, source).build()
52 }
53
54 #[must_use]
56 pub fn packages(&self) -> &[String] {
57 &self.packages
58 }
59
60 #[tracing::instrument(skip_all, fields(path))]
61 fn evaluate_sync(&self, path: &str, input: JsonValue) -> Result<JsonValue, PolicyError> {
62 let path = PolicyPath::parse(path)?;
63 let mut engine = self.engine.clone();
64 engine.set_input(Value::from(input));
65 let value = engine.eval_rule(path.rego_rule()).map_err(engine_error)?;
66 to_json(value)
67 }
68}
69
70impl PolicyClient for RegoPolicyClient {
71 fn evaluate<'a>(
72 &'a self,
73 path: &'a str,
74 input: JsonValue,
75 ) -> BoxFuture<'a, Result<JsonValue, PolicyError>> {
76 let result = self.evaluate_sync(path, input);
77 Box::pin(async move { result })
78 }
79}
80
81fn engine_error(error: impl fmt::Display) -> PolicyError {
82 PolicyError::Engine {
83 message: error.to_string(),
84 }
85}
86
87fn to_json(value: Value) -> Result<JsonValue, PolicyError> {
88 if matches!(value, Value::Undefined) {
89 return Ok(JsonValue::Null);
90 }
91 serde_json::to_value(&value).map_err(|error| PolicyError::Engine {
92 message: format!("could not convert the decision to JSON: {error}"),
93 })
94}
95
96#[derive(Debug, Default)]
98pub struct RegoPolicyClientBuilder {
99 policies: Vec<(String, String)>,
100 data: Vec<JsonValue>,
101}
102
103impl RegoPolicyClientBuilder {
104 #[must_use]
106 pub fn policy(mut self, name: impl Into<String>, source: impl Into<String>) -> Self {
107 self.policies.push((name.into(), source.into()));
108 self
109 }
110
111 #[must_use]
113 pub fn data(mut self, data: JsonValue) -> Self {
114 self.data.push(data);
115 self
116 }
117
118 pub fn build(self) -> Result<RegoPolicyClient, PolicyError> {
125 let mut engine = Engine::new();
126 let mut packages = Vec::with_capacity(self.policies.len());
127 for (name, source) in self.policies {
128 packages.push(engine.add_policy(name, source).map_err(engine_error)?);
129 }
130 for data in self.data {
131 engine.add_data(Value::from(data)).map_err(engine_error)?;
132 }
133 Ok(RegoPolicyClient { engine, packages })
134 }
135}