Skip to main content

json_e/
render.rs

1#![allow(unused_variables)]
2use crate::builtins::BUILTINS;
3use crate::fromnow::{from_now, now};
4use crate::interpreter::{self, Context};
5use crate::op_props::{parse_by, parse_each, parse_each_three};
6use crate::value::{Object, Value};
7use anyhow::{bail, Result};
8use nom::{
9    branch::alt,
10    bytes::complete::tag,
11    character::complete::{alpha1, alphanumeric1},
12    combinator::{all_consuming, recognize},
13    multi::many0,
14    sequence::pair,
15};
16use serde_json::Value as SerdeValue;
17use std::borrow::Cow;
18use std::convert::TryInto;
19use std::fmt::Write;
20
21/// Render the given JSON-e template with the given context.
22pub fn render(template: &SerdeValue, context: &SerdeValue) -> Result<SerdeValue> {
23    let template: Value = template.into();
24    let context = Context::from_serde_value(context, Some(&BUILTINS))?;
25
26    // set "now" in context to a single current time for the duration of the render
27    let mut context = context.child();
28    context.insert("now", Value::String(now()));
29
30    match _render(&template, &context) {
31        // note that this will convert DeletionMarker into Null
32        Ok(v) => Ok(v.try_into()?),
33        Err(e) => Err(e),
34    }
35}
36
37/// Inner, recursive render function.
38fn _render(template: &Value, context: &Context) -> Result<Value> {
39    /// render a value, shaping the result such that it can be used with
40    /// `.filter_map(..).colect::<Result<_>>`.
41    fn render_or_deletion_marker(v: &Value, context: &Context) -> Option<Result<Value>> {
42        match _render(v, context) {
43            Ok(Value::DeletionMarker) => None,
44            Ok(rendered) => Some(Ok(rendered)),
45            Err(e) => Some(Err(e)),
46        }
47    }
48
49    Ok(match template {
50        Value::Number(_) | Value::Bool(_) | Value::Null => (*template).clone(),
51        Value::String(s) => Value::String(interpolate(s, context)?),
52        Value::Array(elements) => Value::Array(
53            elements
54                .iter()
55                .filter_map(|e| render_or_deletion_marker(e, context))
56                .collect::<Result<Vec<Value>>>()?,
57        ),
58        Value::Object(o) => {
59            // first, see if this is a operator invocation
60            for (k, v) in o.iter() {
61                // apply interpolation to key
62                // this allows keys that start with an interpolation to work
63                let interpolated = interpolate(k, context)?;
64                let mut chars = interpolated.chars();
65                if chars.next() == Some('$') && chars.next() != Some('$') {
66                    if let Some(rendered) = maybe_operator(k, v, o, context)? {
67                        return Ok(rendered);
68                    }
69                }
70            }
71
72            // apparently not, so recursively render the content
73            let mut result = Object::new();
74            for (k, v) in o.iter() {
75                // un-escape escaped operators
76                let k = if k.starts_with("$$") { &k[1..] } else { &k[..] };
77                match _render(v, context)? {
78                    Value::DeletionMarker => {}
79                    v => {
80                        result.insert(interpolate(k, context)?, v);
81                    }
82                };
83            }
84            Value::Object(result)
85        }
86
87        // `template` has been converted from JSON and cannot contain DeletionMarker or Function
88        Value::DeletionMarker | Value::Function(_) => unreachable!(),
89    })
90}
91
92/// Perform string interpolation on the given string.
93fn interpolate(mut source: &str, context: &Context) -> Result<String> {
94    // shortcut the common no-interpolation case
95    if source.find('$').is_none() {
96        return Ok(source.into());
97    }
98
99    let mut result = String::new();
100
101    while !source.is_empty() {
102        if let Some(offset) = source.find('$') {
103            // If this is an un-escaped `${`, interpolate..
104            if let Some(s) = source.get(offset..offset + 2) {
105                if s == "${" {
106                    result.push_str(source.get(..offset).unwrap());
107                    let expr = source.get(offset + 2..).unwrap();
108                    let (parsed, remainder) = interpreter::parse_partial(expr)?;
109                    if remainder.get(0..1) != Some("}") {
110                        // Hide '{' in this error message from the formatting machinery in bail macro
111                        let msg = "unterminated ${..} expression";
112                        bail!(msg);
113                    }
114                    let eval_result = interpreter::evaluate(&parsed, context)?;
115
116                    match eval_result {
117                        Value::Number(n) => write!(&mut result, "{}", n)?,
118                        Value::Bool(true) => result.push_str("true"),
119                        Value::Bool(false) => result.push_str("false"),
120                        // null interpolates to an empty string
121                        Value::Null => {}
122                        Value::String(s) => result.push_str(&s),
123                        _ => bail!("interpolation of '{}' produced an array or object", expr),
124                    }
125
126                    source = &remainder[1..];
127                    continue;
128                }
129            }
130
131            // If this is an escape (`$${`), un-escape it
132            if let Some(s) = source.get(offset..offset + 3) {
133                if s == "$${" {
134                    result.push_str(source.get(..offset + 1).unwrap());
135                    source = source.get(offset + 2..).unwrap();
136                    continue;
137                }
138            }
139
140            // otherwise, carry on..
141            result.push_str(source.get(..offset + 1).unwrap());
142            source = source.get(offset + 1..).unwrap();
143        } else {
144            // remainder of the string contains no interpolations..
145            result.push_str(source);
146            source = "";
147        }
148    }
149
150    Ok(result)
151}
152
153/// Evaluate the given expression and return the resulting Value
154fn evaluate(expression: &str, context: &Context) -> Result<Value> {
155    let parsed = interpreter::parse_all(expression)?;
156    interpreter::evaluate(&parsed, context)
157}
158
159/// The given object may be an operator: it has the given key that starts with `$`.  If so,
160/// this function evaluates the operator and return Ok(Some(result)) or an error in
161/// evaluation.  Otherwise, it returns Ok(None) indicating that this is a "normal" object.
162fn maybe_operator(
163    operator: &str,
164    value: &Value,
165    object: &Object,
166    context: &Context,
167) -> Result<Option<Value>> {
168    match operator {
169        "$eval" => Ok(Some(eval_operator(operator, value, object, context)?)),
170        "$flatten" => Ok(Some(flatten_operator(operator, value, object, context)?)),
171        "$flattenDeep" => Ok(Some(flatten_deep_operator(
172            operator, value, object, context,
173        )?)),
174        "$fromNow" => Ok(Some(from_now_operator(operator, value, object, context)?)),
175        "$if" => Ok(Some(if_operator(operator, value, object, context)?)),
176        "$json" => Ok(Some(json_operator(operator, value, object, context)?)),
177        "$let" => Ok(Some(let_operator(operator, value, object, context)?)),
178        "$map" => Ok(Some(map_operator(operator, value, object, context)?)),
179        "$reduce" => Ok(Some(reduce_operator(operator, value, object, context)?)),
180        "$find" => Ok(Some(find_operator(operator, value, object, context)?)),
181        "$match" => Ok(Some(match_operator(operator, value, object, context)?)),
182        "$switch" => Ok(Some(switch_operator(operator, value, object, context)?)),
183        "$merge" => Ok(Some(merge_operator(operator, value, object, context)?)),
184        "$mergeDeep" => Ok(Some(merge_deep_operator(operator, value, object, context)?)),
185        "$reverse" => Ok(Some(reverse_operator(operator, value, object, context)?)),
186        "$sort" => Ok(Some(sort_operator(operator, value, object, context)?)),
187
188        // if the operator isn't recognized, then it should be escaped
189        _ => Err(template_error!(
190            "$<identifier> is reserved; use $$<identifier> ({})",
191            operator
192        )),
193    }
194}
195
196/// Check for undefined properties for an operator, returning an appropriate error message if
197/// found; the check function is called for each value other than the operator.
198#[inline(always)]
199fn check_operator_properties<F>(operator: &str, object: &Object, check: F) -> Result<()>
200where
201    F: Fn(&str) -> bool,
202{
203    // if the object only has one key, we already have it (the operator)
204    if object.len() == 1 {
205        return Ok(());
206    }
207
208    // TODO: avoid this allocation unless necessary
209    let mut unknown = Vec::new();
210
211    for (k, _) in object.iter() {
212        if k == operator {
213            continue;
214        }
215        if !check(k) {
216            unknown.push(k.as_ref());
217        }
218    }
219
220    if !unknown.is_empty() {
221        unknown.sort();
222        Err(template_error!(
223            "{} has undefined properties: {}",
224            operator,
225            unknown.join(" ")
226        ))?;
227    }
228
229    Ok(())
230}
231
232fn eval_operator(
233    operator: &str,
234    value: &Value,
235    object: &Object,
236    context: &Context,
237) -> Result<Value> {
238    check_operator_properties(operator, object, |_| false)?;
239    if let Value::String(expr) = value {
240        Ok(evaluate(expr, context)?)
241    } else {
242        Err(template_error!("$eval must be given a string expression"))
243    }
244}
245
246fn flatten_operator(
247    operator: &str,
248    value: &Value,
249    object: &Object,
250    context: &Context,
251) -> Result<Value> {
252    check_operator_properties(operator, object, |_| false)?;
253    if let Value::Array(ref mut items) = _render(value, context)? {
254        let mut resitems = Vec::new();
255        for mut item in items.drain(..) {
256            if let Value::Array(ref mut subitems) = item {
257                for subitem in subitems.drain(..) {
258                    resitems.push(subitem);
259                }
260            } else {
261                resitems.push(item);
262            }
263        }
264        Ok(Value::Array(resitems))
265    } else {
266        Err(template_error!("$flatten value must evaluate to an array"))
267    }
268}
269
270fn flatten_deep_operator(
271    operator: &str,
272    value: &Value,
273    object: &Object,
274    context: &Context,
275) -> Result<Value> {
276    check_operator_properties(operator, object, |_| false)?;
277
278    fn flatten_deep(mut value: Value, accumulator: &mut Vec<Value>) {
279        if let Value::Array(ref mut items) = value {
280            for item in items.drain(..) {
281                flatten_deep(item, accumulator);
282            }
283        } else {
284            accumulator.push(value);
285        }
286    }
287
288    if let value @ Value::Array(_) = _render(value, context)? {
289        let mut resitems = Vec::new();
290        flatten_deep(value, &mut resitems);
291        Ok(Value::Array(resitems))
292    } else {
293        Err(template_error!("$flatten value must evaluate to an array"))
294    }
295}
296
297fn from_now_operator(
298    operator: &str,
299    value: &Value,
300    object: &Object,
301    context: &Context,
302) -> Result<Value> {
303    check_operator_properties(operator, object, |prop| prop == "from")?;
304    let reference: Cow<str>;
305
306    // if "from" is specified, use that as the reference time
307    if let Some(val) = object.get("from") {
308        match _render(val, context)? {
309            Value::String(ref s) => {
310                reference = Cow::Owned(s.to_string());
311            }
312            _ => {
313                return Err(template_error!("$fromNow expects a string"));
314            }
315        };
316    } else {
317        // otherwise, use `now` from context, which must exist
318        match context.get("now") {
319            None => unreachable!(), // this is set in render()
320            Some(Value::String(ref s)) => reference = Cow::Borrowed(s),
321            _ => return Err(template_error!("context value `now` must be a string")),
322        };
323    }
324
325    match _render(value, context)? {
326        Value::String(s) => Ok(Value::String(from_now(&s, reference.as_ref())?)),
327        _ => Err(template_error!("$fromNow expects a string")),
328    }
329}
330
331fn if_operator(operator: &str, value: &Value, object: &Object, context: &Context) -> Result<Value> {
332    check_operator_properties(operator, object, |prop| prop == "then" || prop == "else")?;
333
334    let eval_result = match value {
335        Value::String(s) => evaluate(s, context)?,
336        _ => return Err(template_error!("$if can evaluate string expressions only")),
337    };
338
339    let prop = if eval_result.into() { "then" } else { "else" };
340    match object.get(prop) {
341        None => Ok(Value::DeletionMarker),
342        Some(val) => Ok(_render(val, context)?),
343    }
344}
345
346fn json_operator(
347    operator: &str,
348    value: &Value,
349    object: &Object,
350    context: &Context,
351) -> Result<Value> {
352    check_operator_properties(operator, object, |_| false)?;
353    let v = _render(value, context)?;
354    Ok(Value::String(v.to_json()?))
355}
356
357fn let_operator(
358    operator: &str,
359    value: &Value,
360    object: &Object,
361    context: &Context,
362) -> Result<Value> {
363    check_operator_properties(operator, object, |p| p == "in")?;
364
365    if !value.is_object() {
366        return Err(template_error!("$let value must be an object"));
367    }
368
369    let value = _render(value, context)?;
370
371    if let Value::Object(o) = value {
372        let mut child_context = context.child();
373        for (k, v) in o.iter() {
374            if !is_identifier(k) {
375                return Err(template_error!(
376                    "top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/"
377                ));
378            }
379            child_context.insert(k, v.clone());
380        }
381
382        if let Some(in_tpl) = object.get("in") {
383            Ok(_render(in_tpl, &child_context)?)
384        } else {
385            Err(template_error!("$let operator requires an `in` clause"))
386        }
387    } else {
388        Err(template_error!("$let value must be an object"))
389    }
390}
391
392fn map_operator(
393    operator: &str,
394    value: &Value,
395    object: &Object,
396    context: &Context,
397) -> Result<Value> {
398    check_operator_properties(operator, object, |p| parse_each(p).is_some())?;
399    if object.len() != 2 {
400        return Err(template_error!("$map must have exactly two properties"));
401    }
402
403    // Unwraps here are safe because the presence of the `each(..)` is checked above.
404    let each_prop = object.keys().find(|k| k != &"$map").unwrap();
405
406    let (value_var, index_var) = parse_each(each_prop)
407        .ok_or_else(|| template_error!("$map requires each(identifier[,identifier]) syntax"))?;
408
409    let each_tpl = object.get(each_prop).unwrap();
410
411    let mut value = _render(value, context)?;
412
413    match value {
414        Value::Object(ref o) => {
415            let mut result = Object::new();
416
417            for (k, v) in o.iter() {
418                let mut subcontext = context.child();
419
420                if let Some(index_var) = index_var {
421                    // if each has two arguments, it gets (val, key)
422                    subcontext.insert(index_var, Value::String(k.to_string()));
423                    subcontext.insert(value_var, v.clone());
424                } else {
425                    // otherwise, it gets ({val: val, key: key})
426                    let mut arg = Object::new();
427                    arg.insert("key".to_string(), Value::String(k.to_string()));
428                    arg.insert("val".to_string(), v.clone());
429                    subcontext.insert(value_var, Value::Object(arg));
430                }
431
432                let rendered = _render(each_tpl, &subcontext)?;
433
434                if let Value::Object(r) = rendered {
435                    for (rk, rv) in r {
436                        result.insert(rk, rv);
437                    }
438                } else {
439                    return Err(template_error!(
440                        "$map on objects expects each(..) to evaluate to an object"
441                    ));
442                }
443            }
444            Ok(Value::Object(result))
445        }
446        Value::Array(ref mut a) => {
447            let mapped = a
448                .drain(..)
449                .enumerate()
450                .map(|(i, v)| {
451                    let mut subcontext = context.child();
452                    subcontext.insert(value_var, v);
453                    if let Some(index_var) = index_var {
454                        subcontext.insert(index_var, Value::Number(i as f64));
455                    }
456                    _render(each_tpl, &subcontext)
457                })
458                .filter(|v| !matches!(v, Ok(Value::DeletionMarker)))
459                .collect::<Result<Vec<_>>>()?;
460            Ok(Value::Array(mapped))
461        }
462        _ => Err(template_error!(
463            "$map value must evaluate to an array or object"
464        )),
465    }
466}
467
468fn reduce_operator(
469    operator: &str,
470    value: &Value,
471    object: &Object,
472    context: &Context,
473) -> Result<Value> {
474    check_operator_properties(operator, object, |p| p == "initial" || parse_each_three(p).is_some())?;
475    if object.len() != 3 {
476        return Err(template_error!("$reduce must have exactly three properties"));
477    }
478
479    // Unwraps here are safe because the presence of the `each(..)` is checked above.
480    let each_prop = object.keys().find(|k| k != &"$reduce" && k != &"initial").unwrap();
481
482    let (acc_var, value_var, index_var) = parse_each_three(each_prop)
483        .ok_or_else(|| template_error!("$reduce requires each(identifier,identifier[,identifier]) syntax"))?;
484
485    let each_tpl = object.get(each_prop).unwrap();
486
487    let mut value = _render(value, context)?;
488    // Need to get the initial value from the object.
489    let initial = object.get("initial").unwrap();
490
491    match value {
492        Value::Array(ref mut a) => {
493            let mapped = a
494                .drain(..)
495                .enumerate()
496                .try_fold(initial.clone(), |acc, (i, v)| {
497                    let mut subcontext = context.child();
498                    subcontext.insert(acc_var, acc.clone());
499                    subcontext.insert(value_var, v);
500                    if let Some(index_var) = index_var {
501                        subcontext.insert(index_var, Value::Number(i as f64));
502                    }
503                    let rendered = _render(each_tpl, &subcontext);
504                    match rendered {
505                        Ok(Value::DeletionMarker) => Ok(acc),
506                        Ok(v) => Ok(v),
507                        Err(e) => Err(e),
508                    }
509                });
510            mapped
511        }
512        _ => Err(template_error!(
513            "$reduce value must evaluate to an array"
514        )),
515    }
516}
517
518fn find_operator(
519    operator: &str,
520    value: &Value,
521    object: &Object,
522    context: &Context,
523) -> Result<Value> {
524    check_operator_properties(operator, object, |p| parse_each(p).is_some())?;
525    if object.len() != 2 {
526        return Err(template_error!("$find must have exactly two properties"));
527    }
528
529    // Unwraps here are safe because the presence of the `each(..)` is checked above.
530    let each_prop = object.keys().find(|k| k != &"$find").unwrap();
531
532    let (value_var, index_var) = parse_each(each_prop)
533        .ok_or_else(|| template_error!("$find requires each(identifier[,identifier]) syntax"))?;
534
535    let each_tpl = object.get(each_prop).unwrap();
536
537    let mut value = _render(value, context)?;
538
539    if let Value::Array(ref mut a) = value {
540        for (i, v) in a.iter().enumerate() {
541            let mut subcontext = context.child();
542            subcontext.insert(value_var, v.clone());
543            if let Some(index_var) = index_var {
544                subcontext.insert(index_var, Value::Number(i as f64));
545            }
546
547            if let Value::String(ref s) = each_tpl {
548                let eval_result = evaluate(s, &subcontext)?;
549                if bool::from(eval_result) {
550                    return _render(v, &subcontext);
551                }
552            } else {
553                return Err(template_error!("$find can evaluate string expressions only"));
554            }
555        }   
556        Ok(Value::DeletionMarker)
557    } else {
558        Err(template_error!("$find value must be an array"))
559    }
560}
561
562fn match_operator(
563    operator: &str,
564    value: &Value,
565    object: &Object,
566    context: &Context,
567) -> Result<Value> {
568    check_operator_properties(operator, object, |_| false)?;
569    if let Value::Object(ref obj) = value {
570        let mut res = vec![];
571        for (cond, val) in obj {
572            if let Ok(cond) = evaluate(cond, context) {
573                if !bool::from(cond) {
574                    continue;
575                }
576                res.push(_render(val, context)?);
577            } else {
578                bail!(template_error!("parsing error in condition"));
579            }
580        }
581        Ok(Value::Array(res))
582    } else {
583        Err(template_error!("$match can evaluate objects only"))
584    }
585}
586
587fn switch_operator(
588    operator: &str,
589    value: &Value,
590    object: &Object,
591    context: &Context,
592) -> Result<Value> {
593    if let Value::Object(ref obj) = value {
594        let mut res = None;
595        let mut unrendered_default = None;
596        for (cond, val) in obj {
597            // if the condition is `$default`, stash it for later
598            if cond == "$default" {
599                unrendered_default = Some(val);
600                continue;
601            }
602            // try to evaluate the condition
603            if let Ok(cond) = evaluate(cond, context) {
604                if !bool::from(cond) {
605                    continue;
606                }
607                if res.is_some() {
608                    bail!(template_error!(
609                        "$switch can only have one truthy condition"
610                    ))
611                }
612                res = Some(val);
613            } else {
614                bail!(template_error!("parsing error in condition"));
615            }
616        }
617
618        if let Some(res) = res {
619            _render(res, context)
620        } else if let Some(unrendered_default) = unrendered_default {
621            _render(unrendered_default, context)
622        } else {
623            Ok(Value::DeletionMarker)
624        }
625    } else {
626        Err(template_error!("$switch can evaluate objects only"))
627    }
628}
629
630fn merge_operator(
631    operator: &str,
632    value: &Value,
633    object: &Object,
634    context: &Context,
635) -> Result<Value> {
636    check_operator_properties(operator, object, |_| false)?;
637    if let Value::Array(items) = _render(value, context)? {
638        let mut new_obj = std::collections::BTreeMap::new();
639        for item in items {
640            if let Value::Object(mut obj) = item {
641                new_obj.append(&mut obj);
642            } else {
643                return Err(template_error!(
644                    "$merge value must evaluate to an array of objects"
645                ));
646            }
647        }
648        Ok(Value::Object(new_obj))
649    } else {
650        Err(template_error!(
651            "$merge value must evaluate to an array of objects"
652        ))
653    }
654}
655
656fn merge_deep_operator(
657    operator: &str,
658    value: &Value,
659    object: &Object,
660    context: &Context,
661) -> Result<Value> {
662    fn merge_deep(a: &Value, b: &Value) -> Value {
663        match (a, b) {
664            (Value::Array(a), Value::Array(b)) => {
665                let mut a = a.clone();
666                a.append(&mut b.clone());
667                Value::Array(a)
668            }
669            (Value::Object(a), Value::Object(b)) => {
670                let mut a = a.clone();
671                let b = b.clone();
672                for (k, v) in b {
673                    if a.contains_key(&k) {
674                        a.insert(k.to_string(), merge_deep(a.get(&k).unwrap(), &v));
675                    } else {
676                        a.insert(k.to_string(), v);
677                    }
678                }
679                Value::Object(a)
680            }
681            _ => b.clone(),
682        }
683    }
684
685    check_operator_properties(operator, object, |_| false)?;
686    if let Value::Array(items) = _render(value, context)? {
687        let mut new_obj = Value::Object(std::collections::BTreeMap::new());
688        for item in items {
689            if let Value::Object(_) = item {
690                new_obj = merge_deep(&new_obj, &item);
691            } else {
692                return Err(template_error!(
693                    "$mergeDeep value must evaluate to an array of objects"
694                ));
695            }
696        }
697        Ok(new_obj)
698    } else {
699        Err(template_error!(
700            "$mergeDeep value must evaluate to an array of objects"
701        ))
702    }
703}
704
705fn reverse_operator(
706    operator: &str,
707    value: &Value,
708    object: &Object,
709    context: &Context,
710) -> Result<Value> {
711    check_operator_properties(operator, object, |_| false)?;
712    if let Value::Array(items) = _render(value, context)? {
713        Ok(Value::Array(items.into_iter().rev().collect()))
714    } else {
715        Err(template_error!("$reverse value must evaluate to an array"))
716    }
717}
718
719fn sort_operator(
720    operator: &str,
721    value: &Value,
722    object: &Object,
723    context: &Context,
724) -> Result<Value> {
725    check_operator_properties(operator, object, |p| parse_by(p).is_some())?;
726
727    let make_err = || {
728        Err(template_error!(
729            "$sorted values to be sorted must have the same type"
730        ))
731    };
732
733    if let Value::Array(arr) = _render(value, context)? {
734        // short-circuit a zero-length array, so we can later assume at least one item
735        if arr.is_empty() {
736            return Ok(Value::Array(arr));
737        }
738
739        if object.len() == 1 {
740            return sort_operator_without_by(operator, arr, object, context);
741        }
742
743        // sort by
744        // Unwraps here are safe because the presence of the `by(..)` is checked above.
745        let by_props: Vec<_> = object.keys().filter(|k| k != &"$sort").collect();
746        if by_props.len() > 1 {
747            return Err(template_error!("only one by(..) is allowed"));
748        }
749
750        let by_var = parse_by(by_props[0])
751            .ok_or_else(|| template_error!("$sort requires by(identifier) syntax"))?;
752
753        let by_expr = if let Value::String(expr) = object.get(by_props[0]).unwrap() {
754            expr
755        } else {
756            return Err(interpreter_error!("invalid expression in $sorted by"));
757        };
758
759        let mut subcontext = context.child();
760
761        // We precompute everything, eval_pairs is a pair with the value after
762        // evaluating the by expression and the original value, so that we can sort
763        // on the first and only take the second when building the final result.
764        // This could be optimized by exiting early if there is an invalid combination of
765        // types.
766        let mut eval_pairs: Vec<(Value, Value)> = arr
767            .iter()
768            .map(|item| {
769                subcontext.insert(by_var, item.clone());
770                Ok((evaluate(by_expr, &subcontext)?, item.clone()))
771            })
772            .collect::<Result<_>>()?;
773
774        if eval_pairs.iter().all(|(e, _v)| e.is_string()) {
775            // sort strings
776            eval_pairs.sort_by(|a, b| {
777                // unwraps are ok because we checked the types above
778                let a = a.0.as_str().unwrap();
779                let b = b.0.as_str().unwrap();
780                a.cmp(b)
781            });
782        } else if eval_pairs.iter().all(|(e, _v)| e.is_number()) {
783            // sort numbers
784            eval_pairs.sort_by(|a, b| {
785                // unwraps are ok because we checked the types above
786                let a = a.0.as_f64().unwrap();
787                let b = b.0.as_f64().unwrap();
788                // unwrap is ok because we do not deal with NaN
789                a.partial_cmp(b).unwrap()
790            });
791        } else {
792            // either a mix of types or unsortable values
793            return make_err();
794        }
795        let result = eval_pairs
796            .into_iter()
797            .map(|(_evaluation, item)| item)
798            .collect();
799        Ok(Value::Array(result))
800    } else {
801        make_err()
802    }
803}
804
805fn sort_operator_without_by(
806    operator: &str,
807    mut arr: Vec<Value>,
808    object: &Object,
809    context: &Context,
810) -> Result<Value> {
811    let make_err = || {
812        Err(template_error!(
813            "$sorted values to be sorted must have the same type"
814        ))
815    };
816    match arr[0] {
817        Value::String(_) => {
818            for i in &arr {
819                if !i.is_string() {
820                    return make_err();
821                }
822            }
823
824            arr.sort_by(|a, b| {
825                // unwraps are ok because we checked the types above
826                let a = a.as_str().unwrap();
827                let b = b.as_str().unwrap();
828                a.cmp(b)
829            });
830            Ok(Value::Array(arr))
831        }
832        Value::Number(_) => {
833            for i in &arr {
834                if !i.is_number() {
835                    return make_err();
836                }
837            }
838
839            arr.sort_by(|a, b| {
840                // unwraps are ok because we checked the types above
841                let a = a.as_f64().unwrap();
842                let b = b.as_f64().unwrap();
843                // unwrap is ok because we do not deal with NaN
844                a.partial_cmp(b).unwrap()
845            });
846            Ok(Value::Array(arr))
847        }
848        _ => make_err(),
849    }
850}
851
852/// Recognize identifier strings for $let
853pub(crate) fn is_identifier(identifier: &str) -> bool {
854    fn parser(input: &str) -> nom::IResult<&str, &str> {
855        all_consuming(recognize(pair(
856            alt((alpha1, tag("_"))),
857            many0(alt((alphanumeric1, tag("_")))),
858        )))(input)
859    }
860
861    if let Ok((remaining, _)) = parser(identifier) {
862        remaining.is_empty()
863    } else {
864        false
865    }
866}
867
868#[cfg(test)]
869mod tests {
870    use super::is_identifier;
871    use crate::render;
872    use serde_json::json;
873
874    #[test]
875    fn render_returns_correct_template() {
876        let template = json!({"code": 200});
877        let context = json!({});
878        assert_eq!(template, render(&template, &context).unwrap())
879    }
880
881    #[test]
882    fn render_gets_number() {
883        let template = json!(200);
884        let context = json!({});
885        assert_eq!(template, render(&template, &context).unwrap())
886    }
887
888    #[test]
889    fn render_gets_boolean() {
890        let template = json!(true);
891        let context = json!({});
892        assert_eq!(template, render(&template, &context).unwrap())
893    }
894
895    #[test]
896    fn render_gets_null() {
897        let template = json!(null);
898        let context = json!({});
899        assert_eq!(template, render(&template, &context).unwrap())
900    }
901
902    #[test]
903    fn render_gets_string() {
904        let template = "tiny string".into();
905        let context = json!({});
906        assert_eq!(template, render(&template, &context).unwrap())
907    }
908
909    #[test]
910    fn render_gets_array() {
911        let template = json!([1, 2, 3]);
912        let context = json!({});
913        assert_eq!(template, render(&template, &context).unwrap())
914    }
915
916    #[test]
917    fn render_gets_object() {
918        let template = json!({"a":1, "b":2});
919        let context = json!({});
920        assert_eq!(template, render(&template, &context).unwrap())
921    }
922
923    #[test]
924    fn invalid_context() {
925        let template = json!({});
926        assert!(render(&template, &json!(null)).is_err());
927        assert!(render(&template, &json!(false)).is_err());
928        assert!(render(&template, &json!(3.2)).is_err());
929        assert!(render(&template, &json!("two")).is_err());
930        assert!(render(&template, &json!([{}])).is_err());
931    }
932
933    #[test]
934    fn render_array_drops_deletion_markers() {
935        let template = json!([1, {"$if": "false", "then": 1}, 3]);
936        let context = json!({});
937        assert_eq!(render(&template, &context).unwrap(), json!([1, 3]))
938    }
939
940    #[test]
941    fn render_obj_drops_deletion_markers() {
942        let template = json!({"v": {"$if": "false", "then": 1}, "k": "sleutel"});
943        let context = json!({});
944        assert_eq!(
945            render(&template, &context).unwrap(),
946            json!({"k": "sleutel"})
947        )
948    }
949
950    mod check_operator_properties {
951        use super::super::{check_operator_properties, Object};
952        use crate::value::Value;
953
954        fn map(mut keys: Vec<&str>) -> Object {
955            let mut map = Object::new();
956            for key in keys.drain(..) {
957                map.insert(key.into(), Value::Null);
958            }
959            map
960        }
961
962        #[test]
963        fn single_property_is_ok() -> anyhow::Result<()> {
964            check_operator_properties("$foo", &map(vec!["$foo"]), |_| false)
965        }
966
967        #[test]
968        fn allowed_properties_are_ok() -> anyhow::Result<()> {
969            check_operator_properties("$foo", &map(vec!["$foo", "a", "b"]), |k| {
970                k == "a" || k == "b"
971            })
972        }
973
974        #[test]
975        fn missing_allowed_properties_are_ok() -> anyhow::Result<()> {
976            check_operator_properties("$foo", &map(vec!["$foo", "b"]), |k| k == "a" || k == "b")
977        }
978
979        #[test]
980        fn disalloewd_properties_not_ok() {
981            assert_template_error!(
982                check_operator_properties("$foo", &map(vec!["$foo", "nosuch"]), |k| k == "a"),
983                "$foo has undefined properties: nosuch",
984            );
985        }
986
987        #[test]
988        fn disalloewd_properties_sorted() {
989            assert_template_error!(
990                check_operator_properties("$foo", &map(vec!["$foo", "a", "b", "c", "d"]), |k| k
991                    == "a"),
992                "$foo has undefined properties: b c d",
993            );
994        }
995    }
996
997    mod interpolate {
998        use super::super::interpolate;
999
1000        use crate::interpreter::Context;
1001        #[test]
1002        fn plain_string() {
1003            let context = Context::new();
1004            assert_eq!(
1005                interpolate("a string", &context).unwrap(),
1006                String::from("a string")
1007            );
1008        }
1009
1010        #[test]
1011        fn interpolation_in_middle() {
1012            let context = Context::new();
1013            assert_eq!(
1014                interpolate("a${13}b", &context).unwrap(),
1015                String::from("a13b")
1016            );
1017        }
1018
1019        #[test]
1020        fn escaped_interpolation() {
1021            let context = Context::new();
1022            assert_eq!(
1023                interpolate("a$${13}b", &context).unwrap(),
1024                String::from("a${13}b")
1025            );
1026        }
1027
1028        #[test]
1029        fn double_escaped_interpolation() {
1030            let context = Context::new();
1031            assert_eq!(
1032                interpolate("a$$${13}b", &context).unwrap(),
1033                String::from("a$${13}b")
1034            );
1035        }
1036
1037        #[test]
1038        fn multibyte_unicode_interpolation_escape() {
1039            let context = Context::new();
1040            assert_eq!(interpolate("a$☃", &context).unwrap(), String::from("a$☃"));
1041        }
1042
1043        #[test]
1044        fn unterminated_interpolation() {
1045            let context = Context::new();
1046            assert!(interpolate("a${13+14", &context).is_err());
1047        }
1048    }
1049
1050    #[test]
1051    fn test_is_identifier() {
1052        assert!(!is_identifier(""));
1053        assert!(!is_identifier("1"));
1054        assert!(!is_identifier("2b"));
1055        assert!(!is_identifier("-"));
1056        assert!(is_identifier("a"));
1057        assert!(is_identifier("abc"));
1058        assert!(is_identifier("abc123"));
1059        assert!(is_identifier("abc_123"));
1060        assert!(!is_identifier("abc-123"));
1061    }
1062}