bracket/render/
context.rs

1//! Context information for the call to a helper.
2use std::ops::Range;
3
4use serde_json::{Map, Value};
5
6use crate::{
7    error::HelperError,
8    helper::HelperResult,
9    json,
10    parser::ast::{Call, Node, Slice},
11    render::assert::{assert, Type},
12};
13
14/// Represents a value to use when a variable lookup fails.
15///
16/// The underlying value is guaranteed to be `Value::String` and
17/// encapsulates the raw value which can be either a path or sub-expression.
18#[derive(Debug, Eq, PartialEq)]
19pub enum MissingValue {
20    /// Stores the raw value for a missing argument.
21    Argument(usize, Value),
22    /// Stores the raw value for a missing parameter.
23    Parameter(String, Value),
24}
25
26/// Property represents a key/value pair.
27///
28/// This is used so that `blockHelperMissing` handlers have access
29/// to the resolved property.
30#[derive(Debug)]
31pub struct Property {
32    /// The path to the property.
33    pub name: String,
34    /// The resolved property value.
35    pub value: Value,
36}
37
38/// Context for the call to a helper exposes immutable access to
39/// the arguments and hash parameters.
40///
41/// It also provides some useful functions for asserting on argument
42/// arity and type.
43pub struct Context<'call> {
44    // TODO: use call to generate context specific errors!
45    call: &'call Call<'call>,
46    name: String,
47    arguments: Vec<Value>,
48    parameters: Map<String, Value>,
49    text: Option<&'call str>,
50    property: Option<Property>,
51    missing: Vec<MissingValue>,
52}
53
54impl<'call> Context<'call> {
55    pub(crate) fn new(
56        call: &'call Call<'call>,
57        name: String,
58        arguments: Vec<Value>,
59        parameters: Map<String, Value>,
60        text: Option<&'call str>,
61        property: Option<Property>,
62        missing: Vec<MissingValue>,
63    ) -> Self {
64        Self {
65            call,
66            name,
67            arguments,
68            parameters,
69            text,
70            property,
71            missing,
72        }
73    }
74
75    /// Get the name for the call.
76    pub fn name(&self) -> &str {
77        &self.name
78    }
79
80    /// Get the list of arguments.
81    pub fn arguments(&self) -> &Vec<Value> {
82        &self.arguments
83    }
84
85    /// Get the map of hash parameters.
86    pub fn parameters(&self) -> &Map<String, Value> {
87        &self.parameters
88    }
89
90    /// Get an argument at an index.
91    pub fn get(&self, index: usize) -> Option<&Value> {
92        self.arguments.get(index)
93    }
94
95    /// Get a hash parameter for the name.
96    pub fn param(&self, name: &str) -> Option<&Value> {
97        self.parameters.get(name)
98    }
99
100    /// Get an argument at an index and use a fallback string
101    /// value when the argument is missing.
102    pub fn get_fallback(&self, index: usize) -> Option<&Value> {
103        let value = self.arguments.get(index);
104        if let Some(&Value::Null) = value {
105            if let Some(value) = self.missing(index) {
106                return Some(value);
107            }
108        }
109        value
110    }
111
112    /// Get a hash parameter for the name and use a fallback string
113    /// value when the parameter is missing.
114    pub fn param_fallback(&self, name: &str) -> Option<&Value> {
115        let value = self.parameters.get(name);
116        if let Some(&Value::Null) = value {
117            if let Some(value) = self.missing_param(name) {
118                return Some(value);
119            }
120        }
121        value
122    }
123
124    /// Get the value for a missing argument.
125    ///
126    /// When the value for an argument is missing it is coerced to
127    /// `Value::Null`; this function allows a helper to distinguish
128    /// between a literal null value and a null resulting from a missing
129    /// value.
130    pub fn missing(&self, index: usize) -> Option<&Value> {
131        for m in self.missing.iter() {
132            if let MissingValue::Argument(ref i, ref value) = m {
133                if i == &index {
134                    return Some(value);
135                }
136            }
137        }
138        None
139    }
140
141    /// Get the value for a missing parameter.
142    ///
143    /// When the value for a parameter is missing it is coerced to
144    /// `Value::Null`; this function allows a helper to distinguish
145    /// between a literal null value and a null resulting from a missing
146    /// value.
147    pub fn missing_param(&self, name: &str) -> Option<&Value> {
148        for m in self.missing.iter() {
149            if let MissingValue::Parameter(ref key, ref value) = m {
150                if key == name {
151                    return Some(value);
152                }
153            }
154        }
155        None
156    }
157
158    /// Get the call syntax tree element.
159    pub fn call(&self) -> &'call Call<'call> {
160        self.call
161    }
162
163    /// Get the raw string value for an argument at an index.
164    pub fn raw(&self, index: usize) -> Option<&str> {
165        self.call.arguments().get(index).map(|v| v.as_str())
166    }
167
168    /// Get the raw string value for a hash parameter with the given name.
169    pub fn raw_param(&self, name: &str) -> Option<&str> {
170        self.call.parameters().get(name).map(|v| v.as_str())
171    }
172
173    /// Get an argument at an index and assert that the value
174    /// is one of the given types.
175    ///
176    /// If no argument exists at the given index the value is
177    /// treated as null and type assertion is performed on the
178    /// null value.
179    pub fn try_get(
180        &self,
181        index: usize,
182        kinds: &[Type],
183    ) -> HelperResult<&Value> {
184        let value = self.arguments.get(index).or(Some(&Value::Null)).unwrap();
185        // TODO: print ErrorInfo code snippet
186        self.assert(value, kinds)?;
187        Ok(value)
188    }
189
190    /// Get a hash parameter for the name and assert that the value
191    /// is one of the given types.
192    ///
193    /// If no parameter exists for the given name the value is
194    /// treated as null and type assertion is performed on the
195    /// null value.
196    pub fn try_param(
197        &self,
198        name: &str,
199        kinds: &[Type],
200    ) -> HelperResult<&Value> {
201        let value = self.parameters.get(name).or(Some(&Value::Null)).unwrap();
202        // TODO: print ErrorInfo code snippet
203        self.assert(value, kinds)?;
204        Ok(value)
205    }
206
207    /// Assert that a value is one of the given kinds.
208    pub fn try_value<'a>(
209        &self,
210        value: &'a Value,
211        kinds: &[Type],
212    ) -> HelperResult<&'a Value> {
213        self.assert(value, kinds)?;
214        Ok(value)
215    }
216
217    /// Get the text for this context.
218    ///
219    /// Only available when invoked as a raw block.
220    pub fn text(&self) -> &Option<&'call str> {
221        &self.text
222    }
223
224    /// Get a resolved property.
225    ///
226    /// Only available to `blockHelperMissing` handlers.
227    pub fn property(&self) -> &Option<Property> {
228        &self.property
229    }
230
231    /// Assert that the call arguments have a valid arity.
232    ///
233    /// If the range start and end are equal than an exact number
234    /// of arguments are expected and a more concise error message
235    /// is used. Range ends are inclusive so 0..1 indicates zero or
236    /// one arguments are allowed.
237    pub fn arity(&self, range: Range<usize>) -> HelperResult<()> {
238        if range.start == range.end {
239            if self.arguments.len() != range.start {
240                return Err(HelperError::ArityExact(
241                    self.name.clone(),
242                    range.start,
243                ));
244            }
245        } else {
246            if self.arguments.len() < range.start
247                || self.arguments.len() > range.end
248            {
249                return Err(HelperError::ArityRange(
250                    self.name.clone(),
251                    range.start,
252                    range.end,
253                ));
254            }
255        }
256        Ok(())
257    }
258
259    /// Assert on the type of a value.
260    pub fn assert(&self, value: &Value, kinds: &[Type]) -> HelperResult<()> {
261        let (result, kind) = assert(value, kinds);
262        if !result {
263            return Err(HelperError::TypeAssert(
264                self.name().to_string(),
265                kind.unwrap(),
266                Type::from(value).to_string(),
267            ));
268        }
269        Ok(())
270    }
271
272    /// Map an optional template to a result.
273    ///
274    /// If the template is `None` this will yield an error; use this
275    /// to assert when an inner block template is required.
276    pub fn assert_block<'a>(
277        &self,
278        template: Option<&'a Node<'a>>,
279    ) -> HelperResult<&'a Node<'a>> {
280        if let Some(node) = template {
281            return Ok(node);
282        }
283        Err(HelperError::BlockTemplate(self.name().to_string()))
284    }
285
286    /// Assert that a block template is empty.
287    ///
288    /// Helpers that do not accept inner block templates can call this 
289    /// to ensure that they are not invoked with the block syntax.
290    pub fn assert_statement<'a>(
291        &self,
292        template: Option<&'a Node<'a>>) -> HelperResult<()> {
293        if template.is_some() {
294            return Err(HelperError::BlockTemplateNotAllowed(self.name().to_string()))
295        }
296        Ok(())
297    }
298
299    /// Lookup a field of a value.
300    ///
301    /// If the target value is not an object or array then this
302    /// will yield `None`.
303    pub fn lookup<'a, S: AsRef<str>>(
304        &self,
305        target: &'a Value,
306        field: S,
307    ) -> Option<&'a Value> {
308        json::find_field(target, field)
309    }
310
311    /// Determine if a value is truthy.
312    pub fn is_truthy(&self, value: &Value) -> bool {
313        json::is_truthy(value)
314    }
315}