milli_core/vector/
json_template.rs

1//! Module to manipulate JSON templates.
2//!
3//! This module allows two main operations:
4//! 1. Render JSON values from a template and a context value.
5//! 2. Retrieve data from a template and JSON values.
6
7#![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/// Encapsulates a JSON template and allows injecting and extracting values from it.
16#[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/// Component of a path to a Value
36#[derive(Debug, Clone)]
37pub enum PathComponent {
38    /// A key inside of an object
39    MapKey(String),
40    /// An index inside of an array
41    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/// Error that occurs when no few value was provided to a template for injection.
57#[derive(Debug)]
58pub struct MissingValue;
59
60/// Error that occurs when trying to parse a template in [`ValueTemplate::new`]
61#[derive(Debug)]
62pub enum TemplateParsingError {
63    /// A repeat string appears inside a repeated value
64    NestedRepeatString(ValuePath),
65    /// A repeat string appears outside of an array
66    RepeatStringNotInArray(ValuePath),
67    /// A repeat string appears in an array, but not in the second position
68    BadIndexForRepeatString(ValuePath, usize),
69    /// A repeated value lacks a placeholder
70    MissingPlaceholderInRepeatedValue(ValuePath),
71    /// Multiple repeat string appear in the template
72    MultipleRepeatString(ValuePath, ValuePath),
73    /// Multiple placeholder strings appear in the template
74    MultiplePlaceholderString(ValuePath, ValuePath),
75    /// No placeholder string appear in the template
76    MissingPlaceholderString,
77    /// A placeholder appears both inside a repeated value and outside of it
78    BothArrayAndSingle {
79        /// Path to the single value
80        single_path: ValuePath,
81        /// Path to the array of repeated values
82        path_to_array: ValuePath,
83        /// Path to placeholder inside each repeated value, starting from the array
84        array_to_placeholder: ValuePath,
85    },
86}
87
88impl TemplateParsingError {
89    /// Produce an error message from the error kind, the name of the root object, the placeholder string and the repeat string
90    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                // note, this case is not super logical, but is also likely to be dead code
183                let single_prepended_path =
184                    prepended_path.iter().cloned().chain(single_path).collect();
185                prepended_path.append(&mut path_to_array);
186                // we don't prepend the array_to_placeholder path as it is the array path that is prepended
187                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/// Error that occurs when [`ValueTemplate::extract`] fails.
198#[derive(Debug)]
199pub struct ExtractionError {
200    /// The cause of the failure
201    pub kind: ExtractionErrorKind,
202    /// The context where the failure happened: the operation that failed
203    pub context: ExtractionErrorContext,
204}
205
206impl ExtractionError {
207    /// Produce an error message from the error, the name of the root object, the placeholder string and the expected value type
208    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
339/// Builds a string representation of a path, preprending the name of the root value.
340pub 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/// Context where an extraction failure happened
361///
362/// The operation that failed
363#[derive(Debug, Clone, Copy)]
364pub enum ExtractionErrorContext {
365    /// Failure happened while extracting a value at a single location
366    ExtractingSingleValue,
367    /// Failure happened while extracting an array of values
368    FindingPathToArray,
369    /// Failure happened while extracting a value inside of an array
370    ExtractingArrayItem(usize),
371}
372
373/// Kind of errors that can happen during extraction
374#[derive(Debug)]
375pub enum ExtractionErrorKind {
376    /// An expected path component is missing
377    MissingPathComponent {
378        /// Index of the missing component in the path
379        missing_index: usize,
380        /// Path where a component is missing
381        path: ValuePath,
382        /// Possible matching key in object
383        key_suggestion: Option<String>,
384    },
385    /// An expected path component cannot be found because its container is the wrong type
386    WrongPathComponent {
387        /// String representation of the wrong component
388        wrong_component: String,
389        /// Index of the wrong component in the path
390        index: usize,
391        /// Path where a component has the wrong type
392        path: ValuePath,
393    },
394    /// Could not deserialize an extracted value to its requested type
395    DeserializationError {
396        /// inner deserialization error
397        error: serde_json::Error,
398        /// path to extracted value
399        path: ValuePath,
400    },
401}
402
403enum ArrayParsingContext<'a> {
404    Nested,
405    NotNested(&'a mut Option<ArrayPath>),
406}
407
408impl ValueTemplate {
409    /// Prepare a template for injection or extraction.
410    ///
411    /// # Parameters
412    ///
413    /// - `template`: JSON value that acts a template. Its placeholder values will be replaced by actual values during injection,
414    ///   and actual values will be recovered from their location during extraction.
415    /// - `placeholder_string`: Value that a JSON string should assume to act as a placeholder value that can be injected into or
416    ///   extracted from.
417    /// - `repeat_string`: Sentinel value that can be placed as the second value in an array to indicate that the first value can be repeated
418    ///   any number of times. The first value should contain exactly one placeholder string.
419    ///
420    /// # Errors
421    ///
422    /// - [`TemplateParsingError`]: refer to the documentation of this type
423    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    /// Whether there is a placeholder that can be repeated.
457    ///
458    /// - During injection, all values are injected in the array placeholder,
459    /// - During extraction, all repeatable placeholders are extracted from the array.
460    pub fn has_array_value(&self) -> bool {
461        matches!(self.value_kind, ValueKind::Array(_))
462    }
463
464    /// Render a value from the template and context values.
465    ///
466    /// # Error
467    ///
468    /// - [`MissingValue`]: if the number of injected values is 0.
469    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                // 1. build the array of repeated values
480                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                // 2. inject at the injection point in the rendered value
491                inject_value(&mut rendered, path_to_array, Value::Array(array));
492            }
493        }
494
495        Ok(rendered)
496    }
497
498    /// Extract sub values from the template and a value.
499    ///
500    /// # Errors
501    ///
502    /// - if a single placeholder is missing.
503    /// - if there is no value corresponding to an array placeholder
504    /// - if the value corresponding to an array placeholder is not an array
505    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                // get the array
526                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(&not_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        // two modes for parsing array.
569        match array {
570            // 1. array contains a repeat string in second position
571            [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            // 2. array does not contain a repeat string
622            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                            // borrow checking limit: the borrow checker cannot be convinced that `object` is no longer mutably borrowed on the
781                            // `else` branch of the `if let`, so we cannot return MissingPathComponent here.
782                            // As a workaround, we checked that the object does not contain the key above, making this `else` unreachable.
783                            unreachable!()
784                        }
785                    } else {
786                        // borrow checking limit: the borrow checker cannot be convinced that `current_value` is no longer mutably borrowed
787                        // on the `else` branch of the `if let`, so we cannot return WrongPathComponent here.
788                        // As a workaround, we checked that the value was not a map above, making this `else` unreachable.
789                        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}