evergreen/common/
settings.rs1use crate as eg;
4use eg::{Editor, EgResult, EgValue};
5use regex::Regex;
6use std::collections::HashMap;
7use std::fmt;
8
9const SETTING_NAME_REGEX: &str = "[^a-zA-Z0-9_\\.]";
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
43pub struct SettingContext {
44 org_id: Option<i64>,
45 user_id: Option<i64>,
46 workstation_id: Option<i64>,
47}
48
49impl fmt::Display for SettingContext {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(
52 f,
53 "SettingContext org={:?} user={:?} workstation={:?}",
54 self.org_id, self.user_id, self.workstation_id
55 )
56 }
57}
58
59impl SettingContext {
60 pub fn new() -> SettingContext {
61 Default::default()
62 }
63 pub fn set_org_id(&mut self, org_id: i64) {
64 self.org_id = Some(org_id);
65 }
66 pub fn set_user_id(&mut self, user_id: i64) {
67 self.user_id = Some(user_id);
68 }
69 pub fn set_workstation_id(&mut self, workstation_id: i64) {
70 self.workstation_id = Some(workstation_id);
71 }
72 pub fn org_id(&self) -> &Option<i64> {
73 &self.org_id
74 }
75 pub fn user_id(&self) -> &Option<i64> {
76 &self.user_id
77 }
78 pub fn workstation_id(&self) -> &Option<i64> {
79 &self.workstation_id
80 }
81 pub fn org_id_value(&self) -> EgValue {
82 self.org_id.map(EgValue::from).unwrap_or(eg::NULL)
83 }
84 pub fn user_id_value(&self) -> EgValue {
85 self.user_id.map(EgValue::from).unwrap_or(eg::NULL)
86 }
87 pub fn workstation_id_value(&self) -> EgValue {
88 self.workstation_id.map(EgValue::from).unwrap_or(eg::NULL)
89 }
90
91 pub fn is_viable(&self) -> bool {
97 self.org_id.is_some() || self.user_id.is_some()
98 }
99}
100
101#[derive(Debug)]
106pub struct SettingEntry {
107 value: EgValue,
108}
109
110impl SettingEntry {
111 pub fn value(&self) -> &EgValue {
112 &self.value
113 }
114}
115
116pub struct Settings {
117 editor: Editor,
118 default_context: SettingContext,
119 name_regex: Option<Regex>,
120 cache: HashMap<SettingContext, HashMap<String, SettingEntry>>,
121}
122
123impl Settings {
124 pub fn new(editor: &Editor) -> Settings {
129 let mut sc = Settings {
130 name_regex: None,
131 editor: editor.clone(),
132 cache: HashMap::new(),
133 default_context: SettingContext::new(),
134 };
135
136 sc.apply_editor(editor);
137
138 sc
139 }
140
141 pub fn set_editor(&mut self, e: &Editor) {
142 self.apply_editor(e);
143 self.editor = e.clone();
144 }
145
146 pub fn apply_editor(&mut self, e: &Editor) {
148 if let Some(reqr) = e.requestor() {
150 if let Ok(id) = reqr.id() {
151 self.default_context.user_id = Some(id);
152 }
153 if let Some(id) = reqr["wsid"].as_int() {
154 self.default_context.workstation_id = Some(id);
155 }
156 if let Some(id) = reqr["ws_ou"].as_int() {
157 self.default_context.org_id = Some(id);
158 } else if let Some(id) = reqr["home_ou"].as_int() {
159 self.default_context.org_id = Some(id);
160 }
161 }
162 }
163
164 pub fn set_org_id(&mut self, org_id: i64) {
166 self.default_context.org_id = Some(org_id);
167 }
168
169 pub fn set_user_id(&mut self, user_id: i64) {
171 self.default_context.user_id = Some(user_id);
172 }
173
174 pub fn set_workstation_id(&mut self, workstation_id: i64) {
176 self.default_context.workstation_id = Some(workstation_id);
177 }
178
179 pub fn reset(&mut self) {
181 self.cache.clear();
182 }
183
184 pub fn get_value(&mut self, name: &str) -> EgResult<&EgValue> {
188 self.get_context_value(&self.default_context.clone(), name)
191 }
192
193 pub fn get_value_at_org(&mut self, name: &str, org_id: i64) -> EgResult<&EgValue> {
195 let mut ctx = SettingContext::new();
196 ctx.set_org_id(org_id);
197 self.get_context_value(&ctx, name)
198 }
199
200 pub fn get_context_value(
202 &mut self,
203 context: &SettingContext,
204 name: &str,
205 ) -> EgResult<&EgValue> {
206 if !self.cache.contains_key(context) {
207 self.cache.insert(context.clone(), HashMap::new());
208 }
209
210 if self.get_cached_value(context, name).is_none() {
211 self.fetch_context_values(context, &[name])?;
213 }
214
215 self.get_cached_value(context, name)
218 .ok_or_else(|| "Setting value missing from cache".to_string().into())
219 }
220
221 pub fn get_cached_value(&mut self, context: &SettingContext, name: &str) -> Option<&EgValue> {
222 let hash = self.cache.get_mut(context)?;
223 hash.get(name).map(|v| v.value())
224 }
225
226 pub fn fetch_values(&mut self, names: &[&str]) -> EgResult<()> {
232 self.fetch_context_values(&self.default_context.clone(), names)
233 }
234
235 pub fn fetch_values_for_org(&mut self, org_id: i64, names: &[&str]) -> EgResult<()> {
237 let mut ctx = SettingContext::new();
238 ctx.set_org_id(org_id);
239
240 let names: Vec<&str> = names
241 .iter()
242 .filter(|n| self.get_cached_value(&ctx, n).is_none())
243 .copied()
244 .collect();
245
246 if names.is_empty() {
247 Ok(())
248 } else {
249 self.fetch_context_values(&ctx, names.as_slice())
250 }
251 }
252
253 pub fn fetch_context_values(
259 &mut self,
260 context: &SettingContext,
261 names: &[&str],
262 ) -> EgResult<()> {
263 if !context.is_viable() {
264 return Err("Cannot retrieve settings without user_id or org_id".into());
265 }
266
267 let user_id = context.user_id_value();
268 let org_id = context.org_id_value();
269 let workstation_id = context.workstation_id_value();
270
271 if self.name_regex.is_none() {
272 self.name_regex = Some(Regex::new(SETTING_NAME_REGEX).unwrap());
274 }
275
276 let reg = self.name_regex.as_ref().unwrap();
277
278 for name in names {
279 if reg.is_match(name) {
280 Err(format!("Invalid setting name: {name}"))?;
281 }
282 }
283
284 let names = format!("{{{}}}", names.join(","));
287
288 let query = eg::hash! {
289 from: [
290 "actor.get_cascade_setting_batch",
291 names, org_id, user_id, workstation_id
292 ]
293 };
294
295 let settings = self.editor.json_query(query)?;
296
297 for set in settings {
298 self.store_setting_value(context, &set)?;
299 }
300
301 Ok(())
302 }
303
304 fn store_setting_value(&mut self, context: &SettingContext, setting: &EgValue) -> EgResult<()> {
305 let value = match setting["value"].as_str() {
306 Some(v) => match EgValue::parse(v) {
307 Ok(vv) => vv,
308 Err(e) => Err(format!("Cannot parse setting value: {e}"))?,
309 },
310 None => EgValue::Null,
311 };
312
313 let name = setting["name"]
314 .as_str()
315 .ok_or_else(|| "Setting has no name".to_string())?;
316
317 let entry = SettingEntry { value };
318
319 let hash = match self.cache.get_mut(context) {
320 Some(h) => h,
321 None => {
322 self.cache.insert(context.clone(), HashMap::new());
323 self.cache.get_mut(context).unwrap()
324 }
325 };
326
327 hash.insert(name.to_string(), entry);
328
329 Ok(())
330 }
331}