comtrya_lib/contexts/
mod.rs

1use anyhow::Result;
2use rhai::Scope;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use tracing::{instrument, trace, warn};
6use user::UserContextProvider;
7
8use crate::contexts::privilege::PrivilegeContextProvider;
9use crate::{
10    config::Config,
11    contexts::{
12        env::EnvContextProvider, os::OSContextProvider,
13        variable_include::VariableIncludeContextProvider, variables::VariablesContextProvider,
14    },
15    values::Value,
16};
17
18pub mod env;
19pub mod os;
20pub mod privilege;
21/// User context provider: understands the user running the command
22pub mod user;
23pub mod variable_include;
24pub mod variables;
25
26pub trait ContextProvider {
27    fn get_prefix(&self) -> String;
28    fn get_contexts(&self) -> Result<Vec<Context>>;
29}
30
31pub type Contexts = BTreeMap<String, BTreeMap<String, Value>>;
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
34pub enum Context {
35    KeyValueContext(String, Value),
36    ListContext(String, Vec<Value>),
37}
38
39#[instrument(skip(config))]
40pub fn build_contexts(config: &Config) -> Contexts {
41    let mut contexts: Contexts = BTreeMap::new();
42
43    let context_providers: Vec<Box<dyn ContextProvider>> = vec![
44        Box::new(UserContextProvider {}),
45        Box::new(OSContextProvider {}),
46        Box::new(EnvContextProvider {}),
47        Box::new(VariablesContextProvider { config }),
48        Box::new(VariableIncludeContextProvider { config }),
49        Box::new(PrivilegeContextProvider { config }),
50    ];
51
52    context_providers.iter().for_each(|provider| {
53        let mut values: BTreeMap<String, Value> = BTreeMap::new();
54
55        provider
56            .get_contexts()
57            .map_err(|e| {
58                warn!(
59                    "Error getting contexts from provider: {} -> {}",
60                    provider.get_prefix(),
61                    e
62                );
63                e
64            })
65            .unwrap_or_default()
66            .iter()
67            .for_each(|context| match context {
68                Context::KeyValueContext(k, v) => {
69                    trace!(
70                        context = provider.get_prefix().as_str(),
71                        key = k.clone().as_str(),
72                        value = v.clone().to_string(),
73                        message = ""
74                    );
75                    values.insert(k.clone(), v.clone());
76                }
77                Context::ListContext(k, v) => {
78                    trace!(
79                        context = provider.get_prefix().as_str(),
80                        key = k.clone().as_str(),
81                        values = format!("{:?}", v), // debug of the vector values is good enough
82                        message = ""
83                    );
84
85                    values.insert(k.clone(), v.clone().into());
86                }
87            });
88
89        contexts.insert(provider.get_prefix(), values);
90    });
91
92    contexts
93}
94
95pub fn to_tera(contexts: &Contexts) -> tera::Context {
96    let mut context = tera::Context::new();
97
98    contexts.iter().for_each(|(m, v)| context.insert(m, v));
99    context
100}
101
102pub fn to_rhai(context: &Contexts) -> rhai::Scope {
103    let mut scope = Scope::new();
104
105    context.iter().for_each(|(m, v)| {
106        let dynamic = match rhai::serde::to_dynamic(v) {
107            Ok(dynamic) => dynamic,
108            Err(error) => {
109                panic!("Failed to convert context value to dynamic: {}", error);
110            }
111        };
112
113        trace!("Add dynamic constant '{}' -> {}", &m, &dynamic);
114
115        scope.push_constant(m.clone(), dynamic);
116    });
117
118    scope
119}
120
121#[cfg(test)]
122mod test {
123    use super::*;
124    use pretty_assertions::assert_eq;
125    use rhai::Engine;
126
127    #[test]
128    fn it_can_convert_to_rhai() {
129        let engine = Engine::new();
130
131        let mut contexts: Contexts = BTreeMap::new();
132        let mut user_context: BTreeMap<String, Value> = BTreeMap::new();
133
134        user_context.insert(String::from("username"), String::from("rawkode").into());
135        contexts.insert(String::from("user"), user_context);
136
137        let mut rhai_context = to_rhai(&contexts);
138        assert!(rhai_context.contains("user"));
139
140        let result = engine
141            .eval_with_scope::<String>(&mut rhai_context, "user.username")
142            .unwrap();
143        assert_eq!(result, String::from("rawkode"));
144    }
145
146    #[test]
147    fn variables_context_resolves_from_config() -> anyhow::Result<()> {
148        let mut variables = BTreeMap::new();
149        variables.insert("ship_name".to_string(), "Jack O'Neill".to_string());
150        variables.insert("ship_captain".to_string(), "Thor".to_string());
151
152        let config = Config {
153            variables,
154            ..Default::default()
155        };
156
157        let contexts = build_contexts(&config);
158        let variables_context_values = contexts.get("variables");
159
160        assert_eq!(variables_context_values.is_some(), true);
161        assert_eq!(
162            variables_context_values
163                .unwrap()
164                .get("ship_name")
165                .unwrap()
166                .to_string(),
167            "Jack O'Neill".to_string()
168        );
169        assert_eq!(
170            variables_context_values
171                .unwrap()
172                .get("ship_captain")
173                .unwrap()
174                .to_string(),
175            "Thor".to_string()
176        );
177
178        Ok(())
179    }
180
181    #[test]
182    fn env_context() -> anyhow::Result<()> {
183        let variables = BTreeMap::new();
184
185        let config = Config {
186            variables,
187            ..Default::default()
188        };
189
190        std::env::set_var("ASCENDED_NAME", "Morgan Le Fay");
191        std::env::set_var("REAL_NAME", "Ganos Lal");
192
193        let contexts = build_contexts(&config);
194        let env_context_values = contexts.get("env");
195
196        assert_eq!(env_context_values.is_some(), true);
197        assert_eq!(
198            env_context_values
199                .unwrap()
200                .get("ASCENDED_NAME")
201                .unwrap()
202                .to_string(),
203            "Morgan Le Fay".to_string()
204        );
205        assert_eq!(
206            env_context_values
207                .unwrap()
208                .get("REAL_NAME")
209                .unwrap()
210                .to_string(),
211            "Ganos Lal".to_string()
212        );
213
214        Ok(())
215    }
216}