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
21pub 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 let mut context = context.child();
28 context.insert("now", Value::String(now()));
29
30 match _render(&template, &context) {
31 Ok(v) => Ok(v.try_into()?),
33 Err(e) => Err(e),
34 }
35}
36
37fn _render(template: &Value, context: &Context) -> Result<Value> {
39 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 for (k, v) in o.iter() {
61 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 let mut result = Object::new();
74 for (k, v) in o.iter() {
75 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 Value::DeletionMarker | Value::Function(_) => unreachable!(),
89 })
90}
91
92fn interpolate(mut source: &str, context: &Context) -> Result<String> {
94 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 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 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 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 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 result.push_str(source.get(..offset + 1).unwrap());
142 source = source.get(offset + 1..).unwrap();
143 } else {
144 result.push_str(source);
146 source = "";
147 }
148 }
149
150 Ok(result)
151}
152
153fn evaluate(expression: &str, context: &Context) -> Result<Value> {
155 let parsed = interpreter::parse_all(expression)?;
156 interpreter::evaluate(&parsed, context)
157}
158
159fn 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 _ => Err(template_error!(
190 "$<identifier> is reserved; use $$<identifier> ({})",
191 operator
192 )),
193 }
194}
195
196#[inline(always)]
199fn check_operator_properties<F>(operator: &str, object: &Object, check: F) -> Result<()>
200where
201 F: Fn(&str) -> bool,
202{
203 if object.len() == 1 {
205 return Ok(());
206 }
207
208 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 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 match context.get("now") {
319 None => unreachable!(), 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 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 subcontext.insert(index_var, Value::String(k.to_string()));
423 subcontext.insert(value_var, v.clone());
424 } else {
425 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 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 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 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 cond == "$default" {
599 unrendered_default = Some(val);
600 continue;
601 }
602 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 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 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 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 eval_pairs.sort_by(|a, b| {
777 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 eval_pairs.sort_by(|a, b| {
785 let a = a.0.as_f64().unwrap();
787 let b = b.0.as_f64().unwrap();
788 a.partial_cmp(b).unwrap()
790 });
791 } else {
792 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 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 let a = a.as_f64().unwrap();
842 let b = b.as_f64().unwrap();
843 a.partial_cmp(b).unwrap()
845 });
846 Ok(Value::Array(arr))
847 }
848 _ => make_err(),
849 }
850}
851
852pub(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}