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}