Skip to main content

expr/
context.rs

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    /// Build a context from any serializable map or struct.
22    #[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
28/// A borrowed source of values used while evaluating an expression.
29///
30/// Implementing this trait allows callers and custom functions to provide
31/// context values without cloning an entire [`Context`] for each evaluation.
32pub trait ContextProvider {
33    fn get(&self, key: &str) -> Option<&Value>;
34
35    /// Materialize the values visible to the expression.
36    ///
37    /// Evaluation only calls this when an expression accesses `$env`.
38    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}