comtrya_lib/contexts/
mod.rs1use 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;
21pub 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), 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}