1use crate::{bail, Error, Result, Value};
2use indexmap::IndexMap;
3use std::fmt::Display;
4
5#[derive(Debug, Clone, Default)]
6pub struct Context(pub(crate) IndexMap<String, Value>);
7
8impl Context {
9 pub fn insert<K, V>(&mut self, key: K, value: V)
10 where
11 K: Into<String>,
12 V: Into<Value>,
13 {
14 self.0.insert(key.into(), value.into());
15 }
16
17 pub fn get(&self, key: &str) -> Option<&Value> {
18 self.0.get(key)
19 }
20
21 #[cfg(feature = "serde")]
23 pub fn from_serialize<T: serde::Serialize + ?Sized>(value: &T) -> Result<Self> {
24 crate::to_value(value)?.try_into()
25 }
26}
27
28pub trait ContextProvider {
33 fn get(&self, key: &str) -> Option<&Value>;
34
35 fn to_context(&self) -> Context;
39
40 #[doc(hidden)]
41 fn environment(&self) -> Context {
42 self.to_context()
43 }
44}
45
46impl ContextProvider for Context {
47 fn get(&self, key: &str) -> Option<&Value> {
48 self.get(key)
49 }
50
51 fn to_context(&self) -> Context {
52 self.clone()
53 }
54}
55
56pub(crate) struct ContextScope<'a> {
57 parent: &'a dyn ContextProvider,
58 values: Context,
59}
60
61impl<'a> ContextScope<'a> {
62 pub(crate) fn new(parent: &'a dyn ContextProvider) -> Self {
63 Self {
64 parent,
65 values: Context::default(),
66 }
67 }
68
69 pub(crate) fn insert<K, V>(&mut self, key: K, value: V)
70 where
71 K: Into<String>,
72 V: Into<Value>,
73 {
74 self.values.insert(key, value);
75 }
76}
77
78impl ContextProvider for ContextScope<'_> {
79 fn get(&self, key: &str) -> Option<&Value> {
80 self.values.get(key).or_else(|| self.parent.get(key))
81 }
82
83 fn to_context(&self) -> Context {
84 let mut context = self.parent.to_context();
85 for (key, value) in &self.values.0 {
86 context.insert(key.clone(), value.clone());
87 }
88 context
89 }
90
91 fn environment(&self) -> Context {
92 self.parent.environment()
93 }
94}
95
96impl TryFrom<Value> for Context {
97 type Error = crate::Error;
98
99 fn try_from(value: Value) -> Result<Self> {
100 match value {
101 Value::Map(values) => Ok(Self(values)),
102 Value::KeyedMap(_) => Err(Error::ExprError(
103 "context keys must be strings".to_string(),
104 )),
105 value => bail!("context must be a map, got {value:?}"),
106 }
107 }
108}
109
110impl<S: Display, T: Into<Value>> FromIterator<(S, T)> for Context {
111 fn from_iter<I: IntoIterator<Item = (S, T)>>(iter: I) -> Self {
112 let mut ctx = Self::default();
113 for (k, v) in iter {
114 ctx.insert(k.to_string(), v);
115 }
116 ctx
117 }
118}
119
120#[cfg(all(test, feature = "serde"))]
121mod tests {
122 use super::Context;
123 use serde::Serialize;
124
125 #[derive(Serialize)]
126 struct Settings<'a> {
127 name: &'a str,
128 retries: u8,
129 }
130
131 #[test]
132 fn context_from_serializable_struct() {
133 let context = Context::from_serialize(&Settings {
134 name: "mise",
135 retries: 3,
136 })
137 .unwrap();
138
139 assert_eq!(context.get("name").unwrap().as_string(), Some("mise"));
140 assert_eq!(context.get("retries").unwrap().as_integer(), Some(3));
141 }
142
143 #[test]
144 fn context_rejects_non_map_values() {
145 assert!(Context::from_serialize(&[1, 2, 3]).is_err());
146 }
147}