1#![warn(rustdoc::broken_intra_doc_links)]
8#![warn(missing_docs)]
9
10use serde::Deserialize;
11use serde_json::{Map, Value};
12
13type ValuePath = Vec<PathComponent>;
14
15#[derive(Debug)]
17pub struct ValueTemplate {
18 template: Value,
19 value_kind: ValueKind,
20}
21
22#[derive(Debug)]
23enum ValueKind {
24 Single(ValuePath),
25 Array(ArrayPath),
26}
27
28#[derive(Debug)]
29struct ArrayPath {
30 repeated_value: Value,
31 path_to_array: ValuePath,
32 value_path_in_array: ValuePath,
33}
34
35#[derive(Debug, Clone)]
37pub enum PathComponent {
38 MapKey(String),
40 ArrayIndex(usize),
42}
43
44impl PartialEq for PathComponent {
45 fn eq(&self, other: &Self) -> bool {
46 match (self, other) {
47 (Self::MapKey(l0), Self::MapKey(r0)) => l0 == r0,
48 (Self::ArrayIndex(l0), Self::ArrayIndex(r0)) => l0 == r0,
49 _ => false,
50 }
51 }
52}
53
54impl Eq for PathComponent {}
55
56#[derive(Debug)]
58pub struct MissingValue;
59
60#[derive(Debug)]
62pub enum TemplateParsingError {
63 NestedRepeatString(ValuePath),
65 RepeatStringNotInArray(ValuePath),
67 BadIndexForRepeatString(ValuePath, usize),
69 MissingPlaceholderInRepeatedValue(ValuePath),
71 MultipleRepeatString(ValuePath, ValuePath),
73 MultiplePlaceholderString(ValuePath, ValuePath),
75 MissingPlaceholderString,
77 BothArrayAndSingle {
79 single_path: ValuePath,
81 path_to_array: ValuePath,
83 array_to_placeholder: ValuePath,
85 },
86}
87
88impl TemplateParsingError {
89 pub fn error_message(&self, root: &str, placeholder: &str, repeat: &str) -> String {
91 match self {
92 TemplateParsingError::NestedRepeatString(path) => {
93 format!(
94 r#"in {}: "{repeat}" appears nested inside of a value that is itself repeated"#,
95 path_with_root(root, path)
96 )
97 }
98 TemplateParsingError::RepeatStringNotInArray(path) => format!(
99 r#"in {}: "{repeat}" appears outside of an array"#,
100 path_with_root(root, path)
101 ),
102 TemplateParsingError::BadIndexForRepeatString(path, index) => format!(
103 r#"in {}: "{repeat}" expected at position #1, but found at position #{index}"#,
104 path_with_root(root, path)
105 ),
106 TemplateParsingError::MissingPlaceholderInRepeatedValue(path) => format!(
107 r#"in {}: Expected "{placeholder}" inside of the repeated value"#,
108 path_with_root(root, path)
109 ),
110 TemplateParsingError::MultipleRepeatString(current, previous) => format!(
111 r#"in {}: Found "{repeat}", but it was already present in {}"#,
112 path_with_root(root, current),
113 path_with_root(root, previous)
114 ),
115 TemplateParsingError::MultiplePlaceholderString(current, previous) => format!(
116 r#"in {}: Found "{placeholder}", but it was already present in {}"#,
117 path_with_root(root, current),
118 path_with_root(root, previous)
119 ),
120 TemplateParsingError::MissingPlaceholderString => {
121 format!(r#"in `{root}`: "{placeholder}" not found"#)
122 }
123 TemplateParsingError::BothArrayAndSingle {
124 single_path,
125 path_to_array,
126 array_to_placeholder,
127 } => {
128 let path_to_first_repeated = path_to_array
129 .iter()
130 .chain(std::iter::once(&PathComponent::ArrayIndex(0)))
131 .chain(array_to_placeholder.iter());
132 format!(
133 r#"in {}: Found "{placeholder}", but it was already present in {} (repeated)"#,
134 path_with_root(root, single_path),
135 path_with_root(root, path_to_first_repeated)
136 )
137 }
138 }
139 }
140
141 fn prepend_path(self, mut prepended_path: ValuePath) -> Self {
142 match self {
143 TemplateParsingError::NestedRepeatString(mut path) => {
144 prepended_path.append(&mut path);
145 TemplateParsingError::NestedRepeatString(prepended_path)
146 }
147 TemplateParsingError::RepeatStringNotInArray(mut path) => {
148 prepended_path.append(&mut path);
149 TemplateParsingError::RepeatStringNotInArray(prepended_path)
150 }
151 TemplateParsingError::BadIndexForRepeatString(mut path, index) => {
152 prepended_path.append(&mut path);
153 TemplateParsingError::BadIndexForRepeatString(prepended_path, index)
154 }
155 TemplateParsingError::MissingPlaceholderInRepeatedValue(mut path) => {
156 prepended_path.append(&mut path);
157 TemplateParsingError::MissingPlaceholderInRepeatedValue(prepended_path)
158 }
159 TemplateParsingError::MultipleRepeatString(mut path, older_path) => {
160 let older_prepended_path =
161 prepended_path.iter().cloned().chain(older_path).collect();
162 prepended_path.append(&mut path);
163 TemplateParsingError::MultipleRepeatString(prepended_path, older_prepended_path)
164 }
165 TemplateParsingError::MultiplePlaceholderString(mut path, older_path) => {
166 let older_prepended_path =
167 prepended_path.iter().cloned().chain(older_path).collect();
168 prepended_path.append(&mut path);
169 TemplateParsingError::MultiplePlaceholderString(
170 prepended_path,
171 older_prepended_path,
172 )
173 }
174 TemplateParsingError::MissingPlaceholderString => {
175 TemplateParsingError::MissingPlaceholderString
176 }
177 TemplateParsingError::BothArrayAndSingle {
178 single_path,
179 mut path_to_array,
180 array_to_placeholder,
181 } => {
182 let single_prepended_path =
184 prepended_path.iter().cloned().chain(single_path).collect();
185 prepended_path.append(&mut path_to_array);
186 TemplateParsingError::BothArrayAndSingle {
188 single_path: single_prepended_path,
189 path_to_array: prepended_path,
190 array_to_placeholder,
191 }
192 }
193 }
194 }
195}
196
197#[derive(Debug)]
199pub struct ExtractionError {
200 pub kind: ExtractionErrorKind,
202 pub context: ExtractionErrorContext,
204}
205
206impl ExtractionError {
207 pub fn error_message(
209 &self,
210 root: &str,
211 placeholder: &str,
212 expected_value_type: &str,
213 ) -> String {
214 let context = match &self.context {
215 ExtractionErrorContext::ExtractingSingleValue => {
216 format!(r#"extracting a single "{placeholder}""#)
217 }
218 ExtractionErrorContext::FindingPathToArray => {
219 format!(r#"extracting the array of "{placeholder}"s"#)
220 }
221 ExtractionErrorContext::ExtractingArrayItem(index) => {
222 format!(r#"extracting item #{index} from the array of "{placeholder}"s"#)
223 }
224 };
225 match &self.kind {
226 ExtractionErrorKind::MissingPathComponent { missing_index, path, key_suggestion } => {
227 let last_named_object = last_named_object(root, path.iter().take(*missing_index));
228 format!(
229 "in {}, while {context}, configuration expects {}, which is missing in response{}",
230 path_with_root(root, path.iter().take(*missing_index)),
231 missing_component(path.get(*missing_index)),
232 match key_suggestion {
233 Some(key_suggestion) => format!("\n - Hint: {last_named_object} has key `{key_suggestion}`, did you mean {} in embedder configuration?",
234 path_with_root(root, path.iter().take(*missing_index).chain(std::iter::once(&PathComponent::MapKey(key_suggestion.to_owned()))))),
235 None => "".to_owned(),
236 }
237 )
238 }
239 ExtractionErrorKind::WrongPathComponent { wrong_component, index, path } => {
240 let last_named_object = last_named_object(root, path.iter().take(*index));
241 format!(
242 "in {}, while {context}, configuration expects {last_named_object} to be {} but server sent {wrong_component}",
243 path_with_root(root, path.iter().take(*index)),
244 expected_component(path.get(*index))
245 )
246 }
247 ExtractionErrorKind::DeserializationError { error, path } => {
248 let last_named_object = last_named_object(root, path);
249 format!(
250 "in {}, while {context}, expected {last_named_object} to be {expected_value_type}, but failed to parse server response:\n - {error}",
251 path_with_root(root, path)
252 )
253 }
254 }
255 }
256}
257
258fn missing_component(component: Option<&PathComponent>) -> String {
259 match component {
260 Some(PathComponent::ArrayIndex(index)) => {
261 format!(r#"item #{index}"#)
262 }
263 Some(PathComponent::MapKey(key)) => {
264 format!(r#"key "{key}""#)
265 }
266 None => "unknown".to_string(),
267 }
268}
269
270fn expected_component(component: Option<&PathComponent>) -> String {
271 match component {
272 Some(PathComponent::ArrayIndex(index)) => {
273 format!(r#"an array with at least {} item(s)"#, index.saturating_add(1))
274 }
275 Some(PathComponent::MapKey(key)) => {
276 format!("an object with key `{}`", key)
277 }
278 None => "unknown".to_string(),
279 }
280}
281
282fn last_named_object<'a>(
283 root: &'a str,
284 path: impl IntoIterator<Item = &'a PathComponent> + 'a,
285) -> LastNamedObject<'a> {
286 let mut last_named_object = LastNamedObject::Object { name: root };
287 for component in path.into_iter() {
288 last_named_object = match (component, last_named_object) {
289 (PathComponent::MapKey(name), _) => LastNamedObject::Object { name },
290 (PathComponent::ArrayIndex(index), LastNamedObject::Object { name }) => {
291 LastNamedObject::ArrayInsideObject { object_name: name, index: *index }
292 }
293 (
294 PathComponent::ArrayIndex(index),
295 LastNamedObject::ArrayInsideObject { object_name, index: _ },
296 ) => LastNamedObject::NestedArrayInsideObject {
297 object_name,
298 index: *index,
299 nesting_level: 0,
300 },
301 (
302 PathComponent::ArrayIndex(index),
303 LastNamedObject::NestedArrayInsideObject { object_name, index: _, nesting_level },
304 ) => LastNamedObject::NestedArrayInsideObject {
305 object_name,
306 index: *index,
307 nesting_level: nesting_level.saturating_add(1),
308 },
309 }
310 }
311 last_named_object
312}
313
314impl std::fmt::Display for LastNamedObject<'_> {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 match self {
317 LastNamedObject::Object { name } => write!(f, "`{name}`"),
318 LastNamedObject::ArrayInsideObject { object_name, index } => {
319 write!(f, "item #{index} inside `{object_name}`")
320 }
321 LastNamedObject::NestedArrayInsideObject { object_name, index, nesting_level } => {
322 if *nesting_level == 0 {
323 write!(f, "item #{index} inside nested array in `{object_name}`")
324 } else {
325 write!(f, "item #{index} inside nested array ({} levels of nesting) in `{object_name}`", nesting_level + 1)
326 }
327 }
328 }
329 }
330}
331
332#[derive(Debug, Clone, Copy)]
333enum LastNamedObject<'a> {
334 Object { name: &'a str },
335 ArrayInsideObject { object_name: &'a str, index: usize },
336 NestedArrayInsideObject { object_name: &'a str, index: usize, nesting_level: usize },
337}
338
339pub fn path_with_root<'a>(
341 root: &str,
342 path: impl IntoIterator<Item = &'a PathComponent> + 'a,
343) -> String {
344 use std::fmt::Write as _;
345 let mut res = format!("`{root}");
346 for component in path.into_iter() {
347 match component {
348 PathComponent::MapKey(key) => {
349 let _ = write!(&mut res, ".{key}");
350 }
351 PathComponent::ArrayIndex(index) => {
352 let _ = write!(&mut res, "[{index}]");
353 }
354 }
355 }
356 res.push('`');
357 res
358}
359
360#[derive(Debug, Clone, Copy)]
364pub enum ExtractionErrorContext {
365 ExtractingSingleValue,
367 FindingPathToArray,
369 ExtractingArrayItem(usize),
371}
372
373#[derive(Debug)]
375pub enum ExtractionErrorKind {
376 MissingPathComponent {
378 missing_index: usize,
380 path: ValuePath,
382 key_suggestion: Option<String>,
384 },
385 WrongPathComponent {
387 wrong_component: String,
389 index: usize,
391 path: ValuePath,
393 },
394 DeserializationError {
396 error: serde_json::Error,
398 path: ValuePath,
400 },
401}
402
403enum ArrayParsingContext<'a> {
404 Nested,
405 NotNested(&'a mut Option<ArrayPath>),
406}
407
408impl ValueTemplate {
409 pub fn new(
424 template: Value,
425 placeholder_string: &str,
426 repeat_string: &str,
427 ) -> Result<Self, TemplateParsingError> {
428 let mut value_path = None;
429 let mut array_path = None;
430 let mut current_path = Vec::new();
431 Self::parse_value(
432 &template,
433 placeholder_string,
434 repeat_string,
435 &mut value_path,
436 &mut ArrayParsingContext::NotNested(&mut array_path),
437 &mut current_path,
438 )?;
439
440 let value_kind = match (array_path, value_path) {
441 (None, None) => return Err(TemplateParsingError::MissingPlaceholderString),
442 (None, Some(value_path)) => ValueKind::Single(value_path),
443 (Some(array_path), None) => ValueKind::Array(array_path),
444 (Some(array_path), Some(value_path)) => {
445 return Err(TemplateParsingError::BothArrayAndSingle {
446 single_path: value_path,
447 path_to_array: array_path.path_to_array,
448 array_to_placeholder: array_path.value_path_in_array,
449 })
450 }
451 };
452
453 Ok(Self { template, value_kind })
454 }
455
456 pub fn has_array_value(&self) -> bool {
461 matches!(self.value_kind, ValueKind::Array(_))
462 }
463
464 pub fn inject(&self, values: impl IntoIterator<Item = Value>) -> Result<Value, MissingValue> {
470 let mut rendered = self.template.clone();
471 let mut values = values.into_iter();
472
473 match &self.value_kind {
474 ValueKind::Single(injection_path) => {
475 let Some(injected_value) = values.next() else { return Err(MissingValue) };
476 inject_value(&mut rendered, injection_path, injected_value);
477 }
478 ValueKind::Array(ArrayPath { repeated_value, path_to_array, value_path_in_array }) => {
479 let mut array = Vec::new();
481 for injected_value in values {
482 let mut repeated_value = repeated_value.clone();
483 inject_value(&mut repeated_value, value_path_in_array, injected_value);
484 array.push(repeated_value);
485 }
486
487 if array.is_empty() {
488 return Err(MissingValue);
489 }
490 inject_value(&mut rendered, path_to_array, Value::Array(array));
492 }
493 }
494
495 Ok(rendered)
496 }
497
498 pub fn extract<T>(&self, mut value: Value) -> Result<Vec<T>, ExtractionError>
506 where
507 T: for<'de> Deserialize<'de>,
508 {
509 Ok(match &self.value_kind {
510 ValueKind::Single(extraction_path) => {
511 let extracted_value =
512 extract_value(extraction_path, &mut value).with_context(|kind| {
513 ExtractionError {
514 kind,
515 context: ExtractionErrorContext::ExtractingSingleValue,
516 }
517 })?;
518 vec![extracted_value]
519 }
520 ValueKind::Array(ArrayPath {
521 repeated_value: _,
522 path_to_array,
523 value_path_in_array,
524 }) => {
525 let array = extract_value(path_to_array, &mut value).with_context(|kind| {
527 ExtractionError { kind, context: ExtractionErrorContext::FindingPathToArray }
528 })?;
529 let array = match array {
530 Value::Array(array) => array,
531 not_array => {
532 let mut path = path_to_array.clone();
533 path.push(PathComponent::ArrayIndex(0));
534 return Err(ExtractionError {
535 kind: ExtractionErrorKind::WrongPathComponent {
536 wrong_component: format_value(¬_array),
537 index: path_to_array.len(),
538 path,
539 },
540 context: ExtractionErrorContext::FindingPathToArray,
541 });
542 }
543 };
544 let mut extracted_values = Vec::with_capacity(array.len());
545
546 for (index, mut item) in array.into_iter().enumerate() {
547 let extracted_value = extract_value(value_path_in_array, &mut item)
548 .with_context(|kind| ExtractionError {
549 kind,
550 context: ExtractionErrorContext::ExtractingArrayItem(index),
551 })?;
552 extracted_values.push(extracted_value);
553 }
554
555 extracted_values
556 }
557 })
558 }
559
560 fn parse_array(
561 array: &[Value],
562 placeholder_string: &str,
563 repeat_string: &str,
564 value_path: &mut Option<ValuePath>,
565 mut array_path: &mut ArrayParsingContext,
566 current_path: &mut ValuePath,
567 ) -> Result<(), TemplateParsingError> {
568 match array {
570 [first, second, rest @ ..] if second == repeat_string => {
572 let ArrayParsingContext::NotNested(array_path) = &mut array_path else {
573 return Err(TemplateParsingError::NestedRepeatString(current_path.clone()));
574 };
575 if let Some(array_path) = array_path {
576 return Err(TemplateParsingError::MultipleRepeatString(
577 current_path.clone(),
578 array_path.path_to_array.clone(),
579 ));
580 }
581 if first == repeat_string {
582 return Err(TemplateParsingError::BadIndexForRepeatString(
583 current_path.clone(),
584 0,
585 ));
586 }
587 if let Some(position) = rest.iter().position(|value| value == repeat_string) {
588 let position = position + 2;
589 return Err(TemplateParsingError::BadIndexForRepeatString(
590 current_path.clone(),
591 position,
592 ));
593 }
594
595 let value_path_in_array = {
596 let mut value_path = None;
597 let mut current_path_in_array = Vec::new();
598
599 Self::parse_value(
600 first,
601 placeholder_string,
602 repeat_string,
603 &mut value_path,
604 &mut ArrayParsingContext::Nested,
605 &mut current_path_in_array,
606 )
607 .map_err(|error| error.prepend_path(current_path.to_vec()))?;
608
609 value_path.ok_or_else(|| {
610 let mut repeated_value_path = current_path.clone();
611 repeated_value_path.push(PathComponent::ArrayIndex(0));
612 TemplateParsingError::MissingPlaceholderInRepeatedValue(repeated_value_path)
613 })?
614 };
615 **array_path = Some(ArrayPath {
616 repeated_value: first.to_owned(),
617 path_to_array: current_path.clone(),
618 value_path_in_array,
619 });
620 }
621 array => {
623 if let Some(position) = array.iter().position(|value| value == repeat_string) {
624 return Err(TemplateParsingError::BadIndexForRepeatString(
625 current_path.clone(),
626 position,
627 ));
628 }
629 for (index, value) in array.iter().enumerate() {
630 current_path.push(PathComponent::ArrayIndex(index));
631 Self::parse_value(
632 value,
633 placeholder_string,
634 repeat_string,
635 value_path,
636 array_path,
637 current_path,
638 )?;
639 current_path.pop();
640 }
641 }
642 }
643 Ok(())
644 }
645
646 fn parse_object(
647 object: &Map<String, Value>,
648 placeholder_string: &str,
649 repeat_string: &str,
650 value_path: &mut Option<ValuePath>,
651 array_path: &mut ArrayParsingContext,
652 current_path: &mut ValuePath,
653 ) -> Result<(), TemplateParsingError> {
654 for (key, value) in object.iter() {
655 current_path.push(PathComponent::MapKey(key.to_owned()));
656 Self::parse_value(
657 value,
658 placeholder_string,
659 repeat_string,
660 value_path,
661 array_path,
662 current_path,
663 )?;
664 current_path.pop();
665 }
666 Ok(())
667 }
668
669 fn parse_value(
670 value: &Value,
671 placeholder_string: &str,
672 repeat_string: &str,
673 value_path: &mut Option<ValuePath>,
674 array_path: &mut ArrayParsingContext,
675 current_path: &mut ValuePath,
676 ) -> Result<(), TemplateParsingError> {
677 match value {
678 Value::String(str) => {
679 if placeholder_string == str {
680 if let Some(value_path) = value_path {
681 return Err(TemplateParsingError::MultiplePlaceholderString(
682 current_path.clone(),
683 value_path.clone(),
684 ));
685 }
686
687 *value_path = Some(current_path.clone());
688 }
689 if repeat_string == str {
690 return Err(TemplateParsingError::RepeatStringNotInArray(current_path.clone()));
691 }
692 }
693 Value::Null | Value::Bool(_) | Value::Number(_) => {}
694 Value::Array(array) => Self::parse_array(
695 array,
696 placeholder_string,
697 repeat_string,
698 value_path,
699 array_path,
700 current_path,
701 )?,
702 Value::Object(object) => Self::parse_object(
703 object,
704 placeholder_string,
705 repeat_string,
706 value_path,
707 array_path,
708 current_path,
709 )?,
710 }
711 Ok(())
712 }
713}
714
715fn inject_value(rendered: &mut Value, injection_path: &Vec<PathComponent>, injected_value: Value) {
716 let mut current_value = rendered;
717 for injection_component in injection_path {
718 current_value = match injection_component {
719 PathComponent::MapKey(key) => current_value.get_mut(key).unwrap(),
720 PathComponent::ArrayIndex(index) => current_value.get_mut(index).unwrap(),
721 }
722 }
723 *current_value = injected_value;
724}
725
726fn format_value(value: &Value) -> String {
727 match value {
728 Value::Array(array) => format!("an array of size {}", array.len()),
729 Value::Object(object) => {
730 format!("an object with {} field(s)", object.len())
731 }
732 value => value.to_string(),
733 }
734}
735
736fn extract_value<T>(
737 extraction_path: &[PathComponent],
738 initial_value: &mut Value,
739) -> Result<T, ExtractionErrorKind>
740where
741 T: for<'de> Deserialize<'de>,
742{
743 let mut current_value = initial_value;
744 for (path_index, extraction_component) in extraction_path.iter().enumerate() {
745 current_value = {
746 match extraction_component {
747 PathComponent::MapKey(key) => {
748 if !current_value.is_object() {
749 return Err(ExtractionErrorKind::WrongPathComponent {
750 wrong_component: format_value(current_value),
751 index: path_index,
752 path: extraction_path.to_vec(),
753 });
754 }
755 if let Some(object) = current_value.as_object_mut() {
756 if !object.contains_key(key) {
757 let typos =
758 levenshtein_automata::LevenshteinAutomatonBuilder::new(2, true)
759 .build_dfa(key);
760 let mut key_suggestion = None;
761 'check_typos: for (key, _) in object.iter() {
762 match typos.eval(key) {
763 levenshtein_automata::Distance::Exact(0) => { }
764 levenshtein_automata::Distance::Exact(_) => {
765 key_suggestion = Some(key.to_owned());
766 break 'check_typos;
767 }
768 levenshtein_automata::Distance::AtLeast(_) => continue,
769 }
770 }
771 return Err(ExtractionErrorKind::MissingPathComponent {
772 missing_index: path_index,
773 path: extraction_path.to_vec(),
774 key_suggestion,
775 });
776 }
777 if let Some(value) = object.get_mut(key) {
778 value
779 } else {
780 unreachable!()
784 }
785 } else {
786 unreachable!()
790 }
791 }
792 PathComponent::ArrayIndex(index) => {
793 if !current_value.is_array() {
794 return Err(ExtractionErrorKind::WrongPathComponent {
795 wrong_component: format_value(current_value),
796 index: path_index,
797 path: extraction_path.to_vec(),
798 });
799 }
800 match current_value.get_mut(index) {
801 Some(value) => value,
802 None => {
803 return Err(ExtractionErrorKind::MissingPathComponent {
804 missing_index: path_index,
805 path: extraction_path.to_vec(),
806 key_suggestion: None,
807 });
808 }
809 }
810 }
811 }
812 };
813 }
814 serde_json::from_value(current_value.take()).map_err(|error| {
815 ExtractionErrorKind::DeserializationError { error, path: extraction_path.to_vec() }
816 })
817}
818
819trait ExtractionResultErrorContext<T> {
820 fn with_context<F>(self, f: F) -> Result<T, ExtractionError>
821 where
822 F: FnOnce(ExtractionErrorKind) -> ExtractionError;
823}
824
825impl<T> ExtractionResultErrorContext<T> for Result<T, ExtractionErrorKind> {
826 fn with_context<F>(self, f: F) -> Result<T, ExtractionError>
827 where
828 F: FnOnce(ExtractionErrorKind) -> ExtractionError,
829 {
830 match self {
831 Ok(t) => Ok(t),
832 Err(kind) => Err(f(kind)),
833 }
834 }
835}
836
837#[cfg(test)]
838mod test {
839 use serde_json::{json, Value};
840
841 use super::{PathComponent, TemplateParsingError, ValueTemplate};
842
843 fn new_template(template: Value) -> Result<ValueTemplate, TemplateParsingError> {
844 ValueTemplate::new(template, "{{text}}", "{{..}}")
845 }
846
847 #[test]
848 fn empty_template() {
849 let template = json!({
850 "toto": "no template at all",
851 "titi": ["this", "will", "not", "work"],
852 "tutu": null
853 });
854
855 let error = new_template(template.clone()).unwrap_err();
856 assert!(matches!(error, TemplateParsingError::MissingPlaceholderString))
857 }
858
859 #[test]
860 fn single_template() {
861 let template = json!({
862 "toto": "text",
863 "titi": ["this", "will", "still", "{{text}}"],
864 "tutu": null
865 });
866
867 let basic = new_template(template.clone()).unwrap();
868
869 assert!(!basic.has_array_value());
870
871 assert_eq!(
872 basic.inject(vec!["work".into(), Value::Null, "test".into()]).unwrap(),
873 json!({
874 "toto": "text",
875 "titi": ["this", "will", "still", "work"],
876 "tutu": null
877 })
878 );
879 }
880
881 #[test]
882 fn too_many_placeholders() {
883 let template = json!({
884 "toto": "{{text}}",
885 "titi": ["this", "will", "still", "{{text}}"],
886 "tutu": "text"
887 });
888
889 match new_template(template.clone()) {
890 Err(TemplateParsingError::MultiplePlaceholderString(left, right)) => {
891 assert_eq!(
892 left,
893 vec![PathComponent::MapKey("titi".into()), PathComponent::ArrayIndex(3)]
894 );
895
896 assert_eq!(right, vec![PathComponent::MapKey("toto".into())])
897 }
898 _ => panic!("should error"),
899 }
900 }
901
902 #[test]
903 fn dynamic_template() {
904 let template = json!({
905 "toto": "text",
906 "titi": [{
907 "type": "text",
908 "data": "{{text}}"
909 }, "{{..}}"],
910 "tutu": null
911 });
912
913 let basic = new_template(template.clone()).unwrap();
914
915 assert!(basic.has_array_value());
916
917 let injected_values = vec![
918 "work".into(),
919 Value::Null,
920 42.into(),
921 "test".into(),
922 "tata".into(),
923 "titi".into(),
924 "tutu".into(),
925 ];
926
927 let rendered = basic.inject(injected_values.clone()).unwrap();
928
929 assert_eq!(
930 rendered,
931 json!({
932 "toto": "text",
933 "titi": [
934 {
935 "type": "text",
936 "data": "work"
937 },
938 {
939 "type": "text",
940 "data": Value::Null
941 },
942 {
943 "type": "text",
944 "data": 42
945 },
946 {
947 "type": "text",
948 "data": "test"
949 },
950 {
951 "type": "text",
952 "data": "tata"
953 },
954 {
955 "type": "text",
956 "data": "titi"
957 },
958 {
959 "type": "text",
960 "data": "tutu"
961 }
962 ],
963 "tutu": null
964 })
965 );
966
967 let extracted_values: Vec<Value> = basic.extract(rendered).unwrap();
968 assert_eq!(extracted_values, injected_values);
969 }
970}