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                .into_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('$') == None {
96        return Ok(source.into());
97    }
98
99    let mut result = String::new();
100
101    while source.len() > 0 {
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).map(|v| v.into())
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.len() > 0 {
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().filter(|k| k != &"$map").next().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| match v {
459                    Ok(Value::DeletionMarker) => false,
460                    _ => true,
461                })
462                .collect::<Result<Vec<_>>>()?;
463            Ok(Value::Array(mapped))
464        }
465        _ => Err(template_error!(
466            "$map value must evaluate to an array or object"
467        )),
468    }
469}
470
471fn reduce_operator(
472    operator: &str,
473    value: &Value,
474    object: &Object,
475    context: &Context,
476) -> Result<Value> {
477    check_operator_properties(operator, object, |p| p == "initial" || parse_each_three(p).is_some())?;
478    if object.len() != 3 {
479        return Err(template_error!("$reduce must have exactly three properties"));
480    }
481
482    // Unwraps here are safe because the presence of the `each(..)` is checked above.
483    let each_prop = object.keys().filter(|k| k != &"$reduce" && k != &"initial").next().unwrap();
484
485    let (acc_var, value_var, index_var) = parse_each_three(each_prop)
486        .ok_or_else(|| template_error!("$reduce requires each(identifier,identifier[,identifier]) syntax"))?;
487
488    let each_tpl = object.get(each_prop).unwrap();
489
490    let mut value = _render(value, context)?;
491    // Need to get the initial value from the object.
492    let initial = object.get("initial").unwrap();
493
494    match value {
495        Value::Array(ref mut a) => {
496            let mapped = a
497                .drain(..)
498                .enumerate()
499                .try_fold(initial.clone(), |acc, (i, v)| {
500                    let mut subcontext = context.child();
501                    subcontext.insert(acc_var, acc.clone());
502                    subcontext.insert(value_var, v);
503                    if let Some(index_var) = index_var {
504                        subcontext.insert(index_var, Value::Number(i as f64));
505                    }
506                    let rendered = _render(each_tpl, &subcontext);
507                    match rendered {
508                        Ok(Value::DeletionMarker) => Ok(acc),
509                        Ok(v) => Ok(v),
510                        Err(e) => Err(e),
511                    }
512                });
513            mapped
514        }
515        _ => Err(template_error!(
516            "$reduce value must evaluate to an array"
517        )),
518    }
519}
520
521fn find_operator(
522    operator: &str,
523    value: &Value,
524    object: &Object,
525    context: &Context,
526) -> Result<Value> {
527    check_operator_properties(operator, object, |p| parse_each(p).is_some())?;
528    if object.len() != 2 {
529        return Err(template_error!("$find must have exactly two properties"));
530    }
531
532    // Unwraps here are safe because the presence of the `each(..)` is checked above.
533    let each_prop = object.keys().filter(|k| k != &"$find").next().unwrap();
534
535    let (value_var, index_var) = parse_each(each_prop)
536        .ok_or_else(|| template_error!("$find requires each(identifier[,identifier]) syntax"))?;
537
538    let each_tpl = object.get(each_prop).unwrap();
539
540    let mut value = _render(value, context)?;
541
542    if let Value::Array(ref mut a) = value {
543        for (i, v) in a.iter().enumerate() {
544            let mut subcontext = context.child();
545            subcontext.insert(value_var, v.clone());
546            if let Some(index_var) = index_var {
547                subcontext.insert(index_var, Value::Number(i as f64));
548            }
549
550            if let Value::String(ref s) = each_tpl {
551                let eval_result = evaluate(&s, &subcontext)?;
552                if bool::from(eval_result) {
553                    return Ok(_render(&v, &subcontext)?);
554                }
555            } else {
556                return Err(template_error!("$find can evaluate string expressions only"));
557            }
558        }   
559        Ok(Value::DeletionMarker)
560    } else {
561        Err(template_error!("$find value must be an array"))
562    }
563}
564
565fn match_operator(
566    operator: &str,
567    value: &Value,
568    object: &Object,
569    context: &Context,
570) -> Result<Value> {
571    check_operator_properties(operator, object, |_| false)?;
572    if let Value::Object(ref obj) = value {
573        let mut res = vec![];
574        for (cond, val) in obj {
575            if let Ok(cond) = evaluate(&cond, context) {
576                if !bool::from(cond) {
577                    continue;
578                }
579                res.push(_render(val, context)?);
580            } else {
581                bail!(template_error!("parsing error in condition"));
582            }
583        }
584        Ok(Value::Array(res))
585    } else {
586        Err(template_error!("$match can evaluate objects only"))
587    }
588}
589
590fn switch_operator(
591    operator: &str,
592    value: &Value,
593    object: &Object,
594    context: &Context,
595) -> Result<Value> {
596    if let Value::Object(ref obj) = value {
597        let mut res = None;
598        let mut unrendered_default = None;
599        for (cond, val) in obj {
600            // if the condition is `$default`, stash it for later
601            if cond == "$default" {
602                unrendered_default = Some(val);
603                continue;
604            }
605            // try to evaluate the condition
606            if let Ok(cond) = evaluate(&cond, context) {
607                if !bool::from(cond) {
608                    continue;
609                }
610                if res.is_some() {
611                    bail!(template_error!(
612                        "$switch can only have one truthy condition"
613                    ))
614                }
615                res = Some(val);
616            } else {
617                bail!(template_error!("parsing error in condition"));
618            }
619        }
620
621        if let Some(res) = res {
622            _render(res, context)
623        } else if let Some(unrendered_default) = unrendered_default {
624            _render(unrendered_default, context)
625        } else {
626            Ok(Value::DeletionMarker)
627        }
628    } else {
629        Err(template_error!("$switch can evaluate objects only"))
630    }
631}
632
633fn merge_operator(
634    operator: &str,
635    value: &Value,
636    object: &Object,
637    context: &Context,
638) -> Result<Value> {
639    check_operator_properties(operator, object, |_| false)?;
640    if let Value::Array(items) = _render(value, context)? {
641        let mut new_obj = std::collections::BTreeMap::new();
642        for item in items {
643            if let Value::Object(mut obj) = item {
644                new_obj.append(&mut obj);
645            } else {
646                return Err(template_error!(
647                    "$merge value must evaluate to an array of objects"
648                ));
649            }
650        }
651        Ok(Value::Object(new_obj))
652    } else {
653        Err(template_error!(
654            "$merge value must evaluate to an array of objects"
655        ))
656    }
657}
658
659fn merge_deep_operator(
660    operator: &str,
661    value: &Value,
662    object: &Object,
663    context: &Context,
664) -> Result<Value> {
665    fn merge_deep(a: &Value, b: &Value) -> Value {
666        match (a, b) {
667            (Value::Array(a), Value::Array(b)) => {
668                let mut a = a.clone();
669                a.append(&mut b.clone());
670                Value::Array(a)
671            }
672            (Value::Object(a), Value::Object(b)) => {
673                let mut a = a.clone();
674                let b = b.clone();
675                for (k, mut v) in b {
676                    if a.contains_key(&k) {
677                        a.insert(k.to_string(), merge_deep(a.get(&k).unwrap(), &mut v));
678                    } else {
679                        a.insert(k.to_string(), v);
680                    }
681                }
682                Value::Object(a)
683            }
684            _ => b.clone(),
685        }
686    }
687
688    check_operator_properties(operator, object, |_| false)?;
689    if let Value::Array(items) = _render(value, context)? {
690        let mut new_obj = Value::Object(std::collections::BTreeMap::new());
691        for item in items {
692            if let Value::Object(_) = item {
693                new_obj = merge_deep(&new_obj, &item);
694            } else {
695                return Err(template_error!(
696                    "$mergeDeep value must evaluate to an array of objects"
697                ));
698            }
699        }
700        Ok(new_obj)
701    } else {
702        Err(template_error!(
703            "$mergeDeep value must evaluate to an array of objects"
704        ))
705    }
706}
707
708fn reverse_operator(
709    operator: &str,
710    value: &Value,
711    object: &Object,
712    context: &Context,
713) -> Result<Value> {
714    check_operator_properties(operator, object, |_| false)?;
715    if let Value::Array(items) = _render(value, context)? {
716        Ok(Value::Array(items.into_iter().rev().collect()))
717    } else {
718        Err(template_error!("$reverse value must evaluate to an array"))
719    }
720}
721
722fn sort_operator(
723    operator: &str,
724    value: &Value,
725    object: &Object,
726    context: &Context,
727) -> Result<Value> {
728    check_operator_properties(operator, object, |p| parse_by(p).is_some())?;
729
730    let make_err = || {
731        Err(template_error!(
732            "$sorted values to be sorted must have the same type"
733        ))
734    };
735
736    if let Value::Array(arr) = _render(value, context)? {
737        // short-circuit a zero-length array, so we can later assume at least one item
738        if arr.len() == 0 {
739            return Ok(Value::Array(arr));
740        }
741
742        if object.len() == 1 {
743            return sort_operator_without_by(operator, arr, object, context);
744        }
745
746        // sort by
747        // Unwraps here are safe because the presence of the `by(..)` is checked above.
748        let by_props: Vec<_> = object.keys().filter(|k| k != &"$sort").collect();
749        if by_props.len() > 1 {
750            return Err(template_error!("only one by(..) is allowed"));
751        }
752
753        let by_var = parse_by(by_props[0])
754            .ok_or_else(|| template_error!("$sort requires by(identifier) syntax"))?;
755
756        let by_expr = if let Value::String(expr) = object.get(by_props[0]).unwrap() {
757            expr
758        } else {
759            return Err(interpreter_error!("invalid expression in $sorted by"));
760        };
761
762        let mut subcontext = context.child();
763
764        // We precompute everything, eval_pairs is a pair with the value after
765        // evaluating the by expression and the original value, so that we can sort
766        // on the first and only take the second when building the final result.
767        // This could be optimized by exiting early if there is an invalid combination of
768        // types.
769        let mut eval_pairs: Vec<(Value, Value)> = arr
770            .iter()
771            .map(|item| {
772                subcontext.insert(by_var, item.clone());
773                Ok((evaluate(by_expr, &subcontext)?, item.clone()))
774            })
775            .collect::<Result<_>>()?;
776
777        if eval_pairs.iter().all(|(e, _v)| e.is_string()) {
778            // sort strings
779            eval_pairs.sort_by(|a, b| {
780                // unwraps are ok because we checked the types above
781                let a = a.0.as_str().unwrap();
782                let b = b.0.as_str().unwrap();
783                a.cmp(b)
784            });
785        } else if eval_pairs.iter().all(|(e, _v)| e.is_number()) {
786            // sort numbers
787            eval_pairs.sort_by(|a, b| {
788                // unwraps are ok because we checked the types above
789                let a = a.0.as_f64().unwrap();
790                let b = b.0.as_f64().unwrap();
791                // unwrap is ok because we do not deal with NaN
792                a.partial_cmp(b).unwrap()
793            });
794        } else {
795            // either a mix of types or unsortable values
796            return make_err();
797        }
798        let result = eval_pairs
799            .into_iter()
800            .map(|(_evaluation, item)| item)
801            .collect();
802        return Ok(Value::Array(result));
803    } else {
804        make_err()
805    }
806}
807
808fn sort_operator_without_by(
809    operator: &str,
810    mut arr: Vec<Value>,
811    object: &Object,
812    context: &Context,
813) -> Result<Value> {
814    let make_err = || {
815        Err(template_error!(
816            "$sorted values to be sorted must have the same type"
817        ))
818    };
819    match arr[0] {
820        Value::String(_) => {
821            for i in &arr {
822                if !i.is_string() {
823                    return make_err();
824                }
825            }
826
827            arr.sort_by(|a, b| {
828                // unwraps are ok because we checked the types above
829                let a = a.as_str().unwrap();
830                let b = b.as_str().unwrap();
831                a.cmp(b)
832            });
833            Ok(Value::Array(arr))
834        }
835        Value::Number(_) => {
836            for i in &arr {
837                if !i.is_number() {
838                    return make_err();
839                }
840            }
841
842            arr.sort_by(|a, b| {
843                // unwraps are ok because we checked the types above
844                let a = a.as_f64().unwrap();
845                let b = b.as_f64().unwrap();
846                // unwrap is ok because we do not deal with NaN
847                a.partial_cmp(b).unwrap()
848            });
849            Ok(Value::Array(arr))
850        }
851        _ => make_err(),
852    }
853}
854
855/// Recognize identifier strings for $let
856pub(crate) fn is_identifier(identifier: &str) -> bool {
857    fn parser(input: &str) -> nom::IResult<&str, &str> {
858        all_consuming(recognize(pair(
859            alt((alpha1, tag("_"))),
860            many0(alt((alphanumeric1, tag("_")))),
861        )))(input)
862    }
863
864    if let Ok((remaining, _)) = parser(identifier) {
865        remaining.is_empty()
866    } else {
867        false
868    }
869}
870
871#[cfg(test)]
872mod tests {
873    use super::is_identifier;
874    use crate::render;
875    use serde_json::json;
876
877    #[test]
878    fn render_returns_correct_template() {
879        let template = json!({"code": 200});
880        let context = json!({});
881        assert_eq!(template, render(&template, &context).unwrap())
882    }
883
884    #[test]
885    fn render_gets_number() {
886        let template = json!(200);
887        let context = json!({});
888        assert_eq!(template, render(&template, &context).unwrap())
889    }
890
891    #[test]
892    fn render_gets_boolean() {
893        let template = json!(true);
894        let context = json!({});
895        assert_eq!(template, render(&template, &context).unwrap())
896    }
897
898    #[test]
899    fn render_gets_null() {
900        let template = json!(null);
901        let context = json!({});
902        assert_eq!(template, render(&template, &context).unwrap())
903    }
904
905    #[test]
906    fn render_gets_string() {
907        let template = "tiny string".into();
908        let context = json!({});
909        assert_eq!(template, render(&template, &context).unwrap())
910    }
911
912    #[test]
913    fn render_gets_array() {
914        let template = json!([1, 2, 3]);
915        let context = json!({});
916        assert_eq!(template, render(&template, &context).unwrap())
917    }
918
919    #[test]
920    fn render_gets_object() {
921        let template = json!({"a":1, "b":2});
922        let context = json!({});
923        assert_eq!(template, render(&template, &context).unwrap())
924    }
925
926    #[test]
927    fn invalid_context() {
928        let template = json!({});
929        assert!(render(&template, &json!(null)).is_err());
930        assert!(render(&template, &json!(false)).is_err());
931        assert!(render(&template, &json!(3.2)).is_err());
932        assert!(render(&template, &json!("two")).is_err());
933        assert!(render(&template, &json!([{}])).is_err());
934    }
935
936    #[test]
937    fn render_array_drops_deletion_markers() {
938        let template = json!([1, {"$if": "false", "then": 1}, 3]);
939        let context = json!({});
940        assert_eq!(render(&template, &context).unwrap(), json!([1, 3]))
941    }
942
943    #[test]
944    fn render_obj_drops_deletion_markers() {
945        let template = json!({"v": {"$if": "false", "then": 1}, "k": "sleutel"});
946        let context = json!({});
947        assert_eq!(
948            render(&template, &context).unwrap(),
949            json!({"k": "sleutel"})
950        )
951    }
952
953    mod check_operator_properties {
954        use super::super::{check_operator_properties, Object};
955        use crate::value::Value;
956
957        fn map(mut keys: Vec<&str>) -> Object {
958            let mut map = Object::new();
959            for key in keys.drain(..) {
960                map.insert(key.into(), Value::Null);
961            }
962            map
963        }
964
965        #[test]
966        fn single_property_is_ok() -> anyhow::Result<()> {
967            check_operator_properties("$foo", &map(vec!["$foo"]), |_| false)
968        }
969
970        #[test]
971        fn allowed_properties_are_ok() -> anyhow::Result<()> {
972            check_operator_properties("$foo", &map(vec!["$foo", "a", "b"]), |k| {
973                k == "a" || k == "b"
974            })
975        }
976
977        #[test]
978        fn missing_allowed_properties_are_ok() -> anyhow::Result<()> {
979            check_operator_properties("$foo", &map(vec!["$foo", "b"]), |k| k == "a" || k == "b")
980        }
981
982        #[test]
983        fn disalloewd_properties_not_ok() {
984            assert_template_error!(
985                check_operator_properties("$foo", &map(vec!["$foo", "nosuch"]), |k| k == "a"),
986                "$foo has undefined properties: nosuch",
987            );
988        }
989
990        #[test]
991        fn disalloewd_properties_sorted() {
992            assert_template_error!(
993                check_operator_properties("$foo", &map(vec!["$foo", "a", "b", "c", "d"]), |k| k
994                    == "a"),
995                "$foo has undefined properties: b c d",
996            );
997        }
998    }
999
1000    mod interpolate {
1001        use super::super::interpolate;
1002
1003        use crate::interpreter::Context;
1004        #[test]
1005        fn plain_string() {
1006            let context = Context::new();
1007            assert_eq!(
1008                interpolate("a string", &context).unwrap(),
1009                String::from("a string")
1010            );
1011        }
1012
1013        #[test]
1014        fn interpolation_in_middle() {
1015            let context = Context::new();
1016            assert_eq!(
1017                interpolate("a${13}b", &context).unwrap(),
1018                String::from("a13b")
1019            );
1020        }
1021
1022        #[test]
1023        fn escaped_interpolation() {
1024            let context = Context::new();
1025            assert_eq!(
1026                interpolate("a$${13}b", &context).unwrap(),
1027                String::from("a${13}b")
1028            );
1029        }
1030
1031        #[test]
1032        fn double_escaped_interpolation() {
1033            let context = Context::new();
1034            assert_eq!(
1035                interpolate("a$$${13}b", &context).unwrap(),
1036                String::from("a$${13}b")
1037            );
1038        }
1039
1040        #[test]
1041        fn multibyte_unicode_interpolation_escape() {
1042            let context = Context::new();
1043            assert_eq!(interpolate("a$☃", &context).unwrap(), String::from("a$☃"));
1044        }
1045
1046        #[test]
1047        fn unterminated_interpolation() {
1048            let context = Context::new();
1049            assert!(interpolate("a${13+14", &context).is_err());
1050        }
1051    }
1052
1053    #[test]
1054    fn test_is_identifier() {
1055        assert!(!is_identifier(""));
1056        assert!(!is_identifier("1"));
1057        assert!(!is_identifier("2b"));
1058        assert!(!is_identifier("-"));
1059        assert!(is_identifier("a"));
1060        assert!(is_identifier("abc"));
1061        assert!(is_identifier("abc123"));
1062        assert!(is_identifier("abc_123"));
1063        assert!(!is_identifier("abc-123"));
1064    }
1065}