1use parking_lot::Mutex;
2use regorus::{Engine, Value};
3use std::{fs, io::ErrorKind};
4use zino_core::{
5 LazyLock,
6 application::{Agent, Application},
7 error::Error,
8};
9
10pub struct RegoEngine {
12 engine: Mutex<Engine>,
14}
15
16impl RegoEngine {
17 #[inline]
19 pub fn new() -> Self {
20 Self {
21 engine: Mutex::new(Engine::default()),
22 }
23 }
24
25 #[inline]
27 pub fn add_policy(
28 &self,
29 path: impl Into<String>,
30 rego: impl Into<String>,
31 ) -> Result<String, Error> {
32 self.engine
33 .lock()
34 .add_policy(path.into(), rego.into())
35 .map_err(|err| Error::new(err.to_string()))
36 }
37
38 #[inline]
40 pub fn add_data(&self, value: impl Into<Value>) -> Result<(), Error> {
41 self.engine
42 .lock()
43 .add_data(value.into())
44 .map_err(|err| Error::new(err.to_string()))
45 }
46
47 #[inline]
49 pub fn add_data_json(&self, data_json: &str) -> Result<(), Error> {
50 self.engine
51 .lock()
52 .add_data_json(data_json)
53 .map_err(|err| Error::new(err.to_string()))
54 }
55
56 #[inline]
58 pub fn clear_data(&self) {
59 self.engine.lock().clear_data()
60 }
61
62 #[inline]
64 pub fn set_input(&self, input: impl Into<Value>) {
65 self.engine.lock().set_input(input.into())
66 }
67
68 #[inline]
70 pub fn set_input_json(&self, input_json: &str) -> Result<(), Error> {
71 self.engine
72 .lock()
73 .set_input_json(input_json)
74 .map_err(|err| Error::new(err.to_string()))
75 }
76
77 #[inline]
79 pub fn eval_rule(&self, path: impl Into<String>) -> Result<Value, Error> {
80 self.engine
81 .lock()
82 .eval_rule(path.into())
83 .map_err(|err| Error::new(err.to_string()))
84 }
85
86 #[inline]
88 pub fn eval_bool_query(&self, query: impl Into<String>) -> Result<bool, Error> {
89 self.engine
90 .lock()
91 .eval_bool_query(query.into(), false)
92 .map_err(|err| Error::new(err.to_string()))
93 }
94
95 #[inline]
97 pub fn eval_allow_query(&self, query: impl Into<String>) -> bool {
98 self.engine.lock().eval_allow_query(query.into(), false)
99 }
100
101 #[inline]
103 pub fn eval_deny_query(&self, query: impl Into<String>) -> bool {
104 self.engine.lock().eval_deny_query(query.into(), false)
105 }
106
107 #[inline]
109 pub fn shared() -> &'static Self {
110 &SHARED_REGO_ENGINE
111 }
112}
113
114static SHARED_REGO_ENGINE: LazyLock<RegoEngine> = LazyLock::new(|| {
116 let engine = RegoEngine::new();
117 let opa_dir = Agent::config_dir().join("opa");
118 match fs::read_dir(opa_dir) {
119 Ok(entries) => {
120 let files = entries.filter_map(|entry| entry.ok());
121 for file in files {
122 let opa_file = file.path();
123 if opa_file.extension().is_some_and(|ext| ext == "rego") {
124 let opa_policy = fs::read_to_string(&opa_file).unwrap_or_else(|err| {
125 let opa_file = opa_file.display();
126 panic!("fail to read the policy file `{opa_file}`: {err}");
127 });
128 let file_name = opa_file
129 .file_name()
130 .map(|s| s.to_string_lossy().into_owned())
131 .unwrap_or_default();
132 engine
133 .add_policy(file_name, opa_policy)
134 .unwrap_or_else(|err| {
135 let opa_file = opa_file.display();
136 panic!("fail to read the policy file `{opa_file}`: {err}");
137 });
138 } else {
139 let opa_data = fs::read_to_string(&opa_file).unwrap_or_else(|err| {
140 let opa_file = opa_file.display();
141 panic!("fail to read the data file `{opa_file}`: {err}");
142 });
143 engine
144 .add_data_json(&opa_data)
145 .expect("fail to add the data document for the OPA");
146 }
147 }
148 }
149 Err(err) => {
150 if err.kind() != ErrorKind::NotFound {
151 tracing::error!("{err}");
152 }
153 }
154 }
155 engine
156});