evergreen/common/
settings.rs

1//! General purpose org / workstation / user setting fetcher and cache.
2//! Primarily uses the 'actor.get_cascade_setting()' DB function.
3use crate as eg;
4use eg::{Editor, EgResult, EgValue};
5use regex::Regex;
6use std::collections::HashMap;
7use std::fmt;
8
9// Setting names consist only of letters, numbers, unders, and dots.
10// This is crucial since the names are encoded as an SQL TEXT[] parameter
11// during lookuping.
12const SETTING_NAME_REGEX: &str = "[^a-zA-Z0-9_\\.]";
13
14/*
15// SettingType may come in handy later when we need to know
16// more about the types.
17#[derive(Debug, Clone, PartialEq)]
18pub struct SettingType {
19    name: String,
20    has_org_setting: bool,
21    has_user_setting: bool,
22    has_workstation_setting: bool,
23}
24
25impl SettingType {
26    pub fn name(&self) -> &str {
27        &self.name
28    }
29    pub fn has_org_setting(&self) -> bool {
30        self.has_org_setting
31    }
32    pub fn has_user_setting(&self) -> bool {
33        self.has_user_setting
34    }
35    pub fn has_workstation_setting(&self) -> bool {
36        self.has_workstation_setting
37    }
38}
39*/
40
41/// Defines the context under which a setting is retrieved.
42#[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    /// Returns true if this context has enough information to
92    /// perform setting lookups.
93    ///
94    /// Workstation ID alone is not enough, since actor.get_cascade_setting()
95    /// requires a user ID for workstation lookups.
96    pub fn is_viable(&self) -> bool {
97        self.org_id.is_some() || self.user_id.is_some()
98    }
99}
100
101///
102///
103/// Each SettingEntry is linked to its runtime context via the HashMap
104/// it's stored in.
105#[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    /// Create a new settings instance from an active Editor.
125    ///
126    /// The Editor instance should be fully setup (e.g. checkauth()
127    /// already run) before using it to create a Settings instance.
128    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    /// Apply context values pulled from the Editor.
147    pub fn apply_editor(&mut self, e: &Editor) {
148        // See if we can pull context data from our editor.
149        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    /// Manually set/override the context org unit ID.
165    pub fn set_org_id(&mut self, org_id: i64) {
166        self.default_context.org_id = Some(org_id);
167    }
168
169    /// Manually set/override the context user ID.
170    pub fn set_user_id(&mut self, user_id: i64) {
171        self.default_context.user_id = Some(user_id);
172    }
173
174    /// Manually set/override the context workstation ID.
175    pub fn set_workstation_id(&mut self, workstation_id: i64) {
176        self.default_context.workstation_id = Some(workstation_id);
177    }
178
179    /// Clear all cached values now.
180    pub fn reset(&mut self) {
181        self.cache.clear();
182    }
183
184    /// Returns a setting value using the default context.
185    ///
186    /// Returns JSON null if no setting exists.
187    pub fn get_value(&mut self, name: &str) -> EgResult<&EgValue> {
188        // Clone needed here because get_context_value mutably borrows
189        // self a number of times.
190        self.get_context_value(&self.default_context.clone(), name)
191    }
192
193    /// Shortcut for get_context_value with an org unit ID set.
194    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    /// Returns a setting value for the provided context.
201    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            // No value in the cache.  Fetch it.
212            self.fetch_context_values(context, &[name])?;
213        }
214
215        // fetch_context_values guarantees a value is applied
216        // for this setting in the cache (defaulting to json null).
217        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    /// Batch setting value fetch.
227    ///
228    /// Returns String Err on load failure or invalid setting name.
229    /// On success, values are stored in the local cache for this
230    /// Setting instance.
231    pub fn fetch_values(&mut self, names: &[&str]) -> EgResult<()> {
232        self.fetch_context_values(&self.default_context.clone(), names)
233    }
234
235    /// Fetch (pre-cache) a batch of values for a given org unit.
236    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    /// Batch setting value fetch.
254    ///
255    /// Returns String Err on load failure or invalid setting name.
256    /// On success, values are stored in the local cache for this
257    /// Setting instance.
258    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            // Avoid recompiling the same regex -- it's not cheap.
273            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        // First param is an SQL TEXT[].
285        // e.g. '{foo.bar,foo.baz}'
286        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}