From f26ab53941c1fa417561081e36a23bd8577e5f74 Mon Sep 17 00:00:00 2001 From: Louis Dureuil Date: Tue, 27 May 2025 18:22:28 +0200 Subject: [PATCH] Rename `ValueTemplate` to `InjectableValue` and add `JsonTemplate` - `InjectableValue`: existing value template where we can inject or extract values at locations designed by a placeholder - `JsonTemplate`: new type that renders all of the strings of a JSON template as liquid templates using a document or a search query --- crates/milli/src/prompt/mod.rs | 2 +- .../injectable_value.rs} | 180 ++++--------- crates/milli/src/vector/json_template/mod.rs | 255 ++++++++++++++++++ crates/milli/src/vector/rest.rs | 27 +- 4 files changed, 329 insertions(+), 135 deletions(-) rename crates/milli/src/vector/{json_template.rs => json_template/injectable_value.rs} (84%) create mode 100644 crates/milli/src/vector/json_template/mod.rs diff --git a/crates/milli/src/prompt/mod.rs b/crates/milli/src/prompt/mod.rs index a5cb8de48..d259a963b 100644 --- a/crates/milli/src/prompt/mod.rs +++ b/crates/milli/src/prompt/mod.rs @@ -10,7 +10,7 @@ use std::fmt::Debug; use std::num::NonZeroUsize; use bumpalo::Bump; -use document::ParseableDocument; +pub(crate) use document::ParseableDocument; use error::{NewPromptError, RenderPromptError}; use fields::{BorrowedFields, OwnedFields}; diff --git a/crates/milli/src/vector/json_template.rs b/crates/milli/src/vector/json_template/injectable_value.rs similarity index 84% rename from crates/milli/src/vector/json_template.rs rename to crates/milli/src/vector/json_template/injectable_value.rs index 179cbe9af..ec7d900db 100644 --- a/crates/milli/src/vector/json_template.rs +++ b/crates/milli/src/vector/json_template/injectable_value.rs @@ -1,20 +1,17 @@ -//! Module to manipulate JSON templates. +//! Module to manipulate JSON values containing placeholder strings. //! //! This module allows two main operations: -//! 1. Render JSON values from a template and a context value. -//! 2. Retrieve data from a template and JSON values. - -#![warn(rustdoc::broken_intra_doc_links)] -#![warn(missing_docs)] +//! 1. Render JSON values from a template value containing placeholders and a value to inject. +//! 2. Extract data from a template value containing placeholders and a concrete JSON value that fits the template value. use serde::Deserialize; use serde_json::{Map, Value}; -type ValuePath = Vec; +use super::{format_value, inject_value, path_with_root, PathComponent, ValuePath}; /// Encapsulates a JSON template and allows injecting and extracting values from it. #[derive(Debug)] -pub struct ValueTemplate { +pub struct InjectableValue { template: Value, value_kind: ValueKind, } @@ -32,34 +29,13 @@ struct ArrayPath { value_path_in_array: ValuePath, } -/// Component of a path to a Value -#[derive(Debug, Clone)] -pub enum PathComponent { - /// A key inside of an object - MapKey(String), - /// An index inside of an array - ArrayIndex(usize), -} - -impl PartialEq for PathComponent { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::MapKey(l0), Self::MapKey(r0)) => l0 == r0, - (Self::ArrayIndex(l0), Self::ArrayIndex(r0)) => l0 == r0, - _ => false, - } - } -} - -impl Eq for PathComponent {} - -/// Error that occurs when no few value was provided to a template for injection. +/// Error that occurs when no value was provided to a template for injection. #[derive(Debug)] pub struct MissingValue; -/// Error that occurs when trying to parse a template in [`ValueTemplate::new`] +/// Error that occurs when trying to parse a template in [`InjectableValue::new`] #[derive(Debug)] -pub enum TemplateParsingError { +pub enum InjectableParsingError { /// A repeat string appears inside a repeated value NestedRepeatString(ValuePath), /// A repeat string appears outside of an array @@ -85,42 +61,42 @@ pub enum TemplateParsingError { }, } -impl TemplateParsingError { +impl InjectableParsingError { /// Produce an error message from the error kind, the name of the root object, the placeholder string and the repeat string pub fn error_message(&self, root: &str, placeholder: &str, repeat: &str) -> String { match self { - TemplateParsingError::NestedRepeatString(path) => { + InjectableParsingError::NestedRepeatString(path) => { format!( r#"in {}: "{repeat}" appears nested inside of a value that is itself repeated"#, path_with_root(root, path) ) } - TemplateParsingError::RepeatStringNotInArray(path) => format!( + InjectableParsingError::RepeatStringNotInArray(path) => format!( r#"in {}: "{repeat}" appears outside of an array"#, path_with_root(root, path) ), - TemplateParsingError::BadIndexForRepeatString(path, index) => format!( + InjectableParsingError::BadIndexForRepeatString(path, index) => format!( r#"in {}: "{repeat}" expected at position #1, but found at position #{index}"#, path_with_root(root, path) ), - TemplateParsingError::MissingPlaceholderInRepeatedValue(path) => format!( + InjectableParsingError::MissingPlaceholderInRepeatedValue(path) => format!( r#"in {}: Expected "{placeholder}" inside of the repeated value"#, path_with_root(root, path) ), - TemplateParsingError::MultipleRepeatString(current, previous) => format!( + InjectableParsingError::MultipleRepeatString(current, previous) => format!( r#"in {}: Found "{repeat}", but it was already present in {}"#, path_with_root(root, current), path_with_root(root, previous) ), - TemplateParsingError::MultiplePlaceholderString(current, previous) => format!( + InjectableParsingError::MultiplePlaceholderString(current, previous) => format!( r#"in {}: Found "{placeholder}", but it was already present in {}"#, path_with_root(root, current), path_with_root(root, previous) ), - TemplateParsingError::MissingPlaceholderString => { + InjectableParsingError::MissingPlaceholderString => { format!(r#"in `{root}`: "{placeholder}" not found"#) } - TemplateParsingError::BothArrayAndSingle { + InjectableParsingError::BothArrayAndSingle { single_path, path_to_array, array_to_placeholder, @@ -140,41 +116,41 @@ impl TemplateParsingError { fn prepend_path(self, mut prepended_path: ValuePath) -> Self { match self { - TemplateParsingError::NestedRepeatString(mut path) => { + InjectableParsingError::NestedRepeatString(mut path) => { prepended_path.append(&mut path); - TemplateParsingError::NestedRepeatString(prepended_path) + InjectableParsingError::NestedRepeatString(prepended_path) } - TemplateParsingError::RepeatStringNotInArray(mut path) => { + InjectableParsingError::RepeatStringNotInArray(mut path) => { prepended_path.append(&mut path); - TemplateParsingError::RepeatStringNotInArray(prepended_path) + InjectableParsingError::RepeatStringNotInArray(prepended_path) } - TemplateParsingError::BadIndexForRepeatString(mut path, index) => { + InjectableParsingError::BadIndexForRepeatString(mut path, index) => { prepended_path.append(&mut path); - TemplateParsingError::BadIndexForRepeatString(prepended_path, index) + InjectableParsingError::BadIndexForRepeatString(prepended_path, index) } - TemplateParsingError::MissingPlaceholderInRepeatedValue(mut path) => { + InjectableParsingError::MissingPlaceholderInRepeatedValue(mut path) => { prepended_path.append(&mut path); - TemplateParsingError::MissingPlaceholderInRepeatedValue(prepended_path) + InjectableParsingError::MissingPlaceholderInRepeatedValue(prepended_path) } - TemplateParsingError::MultipleRepeatString(mut path, older_path) => { + InjectableParsingError::MultipleRepeatString(mut path, older_path) => { let older_prepended_path = prepended_path.iter().cloned().chain(older_path).collect(); prepended_path.append(&mut path); - TemplateParsingError::MultipleRepeatString(prepended_path, older_prepended_path) + InjectableParsingError::MultipleRepeatString(prepended_path, older_prepended_path) } - TemplateParsingError::MultiplePlaceholderString(mut path, older_path) => { + InjectableParsingError::MultiplePlaceholderString(mut path, older_path) => { let older_prepended_path = prepended_path.iter().cloned().chain(older_path).collect(); prepended_path.append(&mut path); - TemplateParsingError::MultiplePlaceholderString( + InjectableParsingError::MultiplePlaceholderString( prepended_path, older_prepended_path, ) } - TemplateParsingError::MissingPlaceholderString => { - TemplateParsingError::MissingPlaceholderString + InjectableParsingError::MissingPlaceholderString => { + InjectableParsingError::MissingPlaceholderString } - TemplateParsingError::BothArrayAndSingle { + InjectableParsingError::BothArrayAndSingle { single_path, mut path_to_array, array_to_placeholder, @@ -184,7 +160,7 @@ impl TemplateParsingError { prepended_path.iter().cloned().chain(single_path).collect(); prepended_path.append(&mut path_to_array); // we don't prepend the array_to_placeholder path as it is the array path that is prepended - TemplateParsingError::BothArrayAndSingle { + InjectableParsingError::BothArrayAndSingle { single_path: single_prepended_path, path_to_array: prepended_path, array_to_placeholder, @@ -194,7 +170,7 @@ impl TemplateParsingError { } } -/// Error that occurs when [`ValueTemplate::extract`] fails. +/// Error that occurs when [`InjectableValue::extract`] fails. #[derive(Debug)] pub struct ExtractionError { /// The cause of the failure @@ -336,27 +312,6 @@ enum LastNamedObject<'a> { NestedArrayInsideObject { object_name: &'a str, index: usize, nesting_level: usize }, } -/// Builds a string representation of a path, preprending the name of the root value. -pub fn path_with_root<'a>( - root: &str, - path: impl IntoIterator + 'a, -) -> String { - use std::fmt::Write as _; - let mut res = format!("`{root}"); - for component in path.into_iter() { - match component { - PathComponent::MapKey(key) => { - let _ = write!(&mut res, ".{key}"); - } - PathComponent::ArrayIndex(index) => { - let _ = write!(&mut res, "[{index}]"); - } - } - } - res.push('`'); - res -} - /// Context where an extraction failure happened /// /// The operation that failed @@ -405,7 +360,7 @@ enum ArrayParsingContext<'a> { NotNested(&'a mut Option), } -impl ValueTemplate { +impl InjectableValue { /// Prepare a template for injection or extraction. /// /// # Parameters @@ -419,12 +374,12 @@ impl ValueTemplate { /// /// # Errors /// - /// - [`TemplateParsingError`]: refer to the documentation of this type + /// - [`InjectableParsingError`]: refer to the documentation of this type pub fn new( template: Value, placeholder_string: &str, repeat_string: &str, - ) -> Result { + ) -> Result { let mut value_path = None; let mut array_path = None; let mut current_path = Vec::new(); @@ -438,11 +393,11 @@ impl ValueTemplate { )?; let value_kind = match (array_path, value_path) { - (None, None) => return Err(TemplateParsingError::MissingPlaceholderString), + (None, None) => return Err(InjectableParsingError::MissingPlaceholderString), (None, Some(value_path)) => ValueKind::Single(value_path), (Some(array_path), None) => ValueKind::Array(array_path), (Some(array_path), Some(value_path)) => { - return Err(TemplateParsingError::BothArrayAndSingle { + return Err(InjectableParsingError::BothArrayAndSingle { single_path: value_path, path_to_array: array_path.path_to_array, array_to_placeholder: array_path.value_path_in_array, @@ -564,29 +519,29 @@ impl ValueTemplate { value_path: &mut Option, mut array_path: &mut ArrayParsingContext, current_path: &mut ValuePath, - ) -> Result<(), TemplateParsingError> { + ) -> Result<(), InjectableParsingError> { // two modes for parsing array. match array { // 1. array contains a repeat string in second position [first, second, rest @ ..] if second == repeat_string => { let ArrayParsingContext::NotNested(array_path) = &mut array_path else { - return Err(TemplateParsingError::NestedRepeatString(current_path.clone())); + return Err(InjectableParsingError::NestedRepeatString(current_path.clone())); }; if let Some(array_path) = array_path { - return Err(TemplateParsingError::MultipleRepeatString( + return Err(InjectableParsingError::MultipleRepeatString( current_path.clone(), array_path.path_to_array.clone(), )); } if first == repeat_string { - return Err(TemplateParsingError::BadIndexForRepeatString( + return Err(InjectableParsingError::BadIndexForRepeatString( current_path.clone(), 0, )); } if let Some(position) = rest.iter().position(|value| value == repeat_string) { let position = position + 2; - return Err(TemplateParsingError::BadIndexForRepeatString( + return Err(InjectableParsingError::BadIndexForRepeatString( current_path.clone(), position, )); @@ -609,7 +564,9 @@ impl ValueTemplate { value_path.ok_or_else(|| { let mut repeated_value_path = current_path.clone(); repeated_value_path.push(PathComponent::ArrayIndex(0)); - TemplateParsingError::MissingPlaceholderInRepeatedValue(repeated_value_path) + InjectableParsingError::MissingPlaceholderInRepeatedValue( + repeated_value_path, + ) })? }; **array_path = Some(ArrayPath { @@ -621,7 +578,7 @@ impl ValueTemplate { // 2. array does not contain a repeat string array => { if let Some(position) = array.iter().position(|value| value == repeat_string) { - return Err(TemplateParsingError::BadIndexForRepeatString( + return Err(InjectableParsingError::BadIndexForRepeatString( current_path.clone(), position, )); @@ -650,7 +607,7 @@ impl ValueTemplate { value_path: &mut Option, array_path: &mut ArrayParsingContext, current_path: &mut ValuePath, - ) -> Result<(), TemplateParsingError> { + ) -> Result<(), InjectableParsingError> { for (key, value) in object.iter() { current_path.push(PathComponent::MapKey(key.to_owned())); Self::parse_value( @@ -673,12 +630,12 @@ impl ValueTemplate { value_path: &mut Option, array_path: &mut ArrayParsingContext, current_path: &mut ValuePath, - ) -> Result<(), TemplateParsingError> { + ) -> Result<(), InjectableParsingError> { match value { Value::String(str) => { if placeholder_string == str { if let Some(value_path) = value_path { - return Err(TemplateParsingError::MultiplePlaceholderString( + return Err(InjectableParsingError::MultiplePlaceholderString( current_path.clone(), value_path.clone(), )); @@ -687,7 +644,9 @@ impl ValueTemplate { *value_path = Some(current_path.clone()); } if repeat_string == str { - return Err(TemplateParsingError::RepeatStringNotInArray(current_path.clone())); + return Err(InjectableParsingError::RepeatStringNotInArray( + current_path.clone(), + )); } } Value::Null | Value::Bool(_) | Value::Number(_) => {} @@ -712,27 +671,6 @@ impl ValueTemplate { } } -fn inject_value(rendered: &mut Value, injection_path: &Vec, injected_value: Value) { - let mut current_value = rendered; - for injection_component in injection_path { - current_value = match injection_component { - PathComponent::MapKey(key) => current_value.get_mut(key).unwrap(), - PathComponent::ArrayIndex(index) => current_value.get_mut(index).unwrap(), - } - } - *current_value = injected_value; -} - -fn format_value(value: &Value) -> String { - match value { - Value::Array(array) => format!("an array of size {}", array.len()), - Value::Object(object) => { - format!("an object with {} field(s)", object.len()) - } - value => value.to_string(), - } -} - fn extract_value( extraction_path: &[PathComponent], initial_value: &mut Value, @@ -838,10 +776,10 @@ impl ExtractionResultErrorContext for Result { mod test { use serde_json::{json, Value}; - use super::{PathComponent, TemplateParsingError, ValueTemplate}; + use super::{InjectableParsingError, InjectableValue, PathComponent}; - fn new_template(template: Value) -> Result { - ValueTemplate::new(template, "{{text}}", "{{..}}") + fn new_template(template: Value) -> Result { + InjectableValue::new(template, "{{text}}", "{{..}}") } #[test] @@ -853,7 +791,7 @@ mod test { }); let error = new_template(template.clone()).unwrap_err(); - assert!(matches!(error, TemplateParsingError::MissingPlaceholderString)) + assert!(matches!(error, InjectableParsingError::MissingPlaceholderString)) } #[test] @@ -887,7 +825,7 @@ mod test { }); match new_template(template.clone()) { - Err(TemplateParsingError::MultiplePlaceholderString(left, right)) => { + Err(InjectableParsingError::MultiplePlaceholderString(left, right)) => { assert_eq!( left, vec![PathComponent::MapKey("titi".into()), PathComponent::ArrayIndex(3)] diff --git a/crates/milli/src/vector/json_template/mod.rs b/crates/milli/src/vector/json_template/mod.rs new file mode 100644 index 000000000..053593321 --- /dev/null +++ b/crates/milli/src/vector/json_template/mod.rs @@ -0,0 +1,255 @@ +//! Exposes types to manipulate JSON values +//! +//! - [`JsonTemplate`]: renders JSON values by rendering its strings as [`Template`]s. +//! - [`InjectableValue`]: Describes a JSON value containing placeholders, +//! then allows to inject values instead of the placeholder to produce new concrete JSON values, +//! or extract sub-values at the placeholder location from concrete JSON values. +//! +//! The module also exposes foundational types to work with JSON paths: +//! +//! - [`ValuePath`] is made of [`PathComponent`]s to indicate the location of a sub-value inside of a JSON value. +//! - [`inject_value`] is a primitive that replaces the sub-value at the described location by an injected value. + +#![warn(rustdoc::broken_intra_doc_links)] +#![warn(missing_docs)] + +use bumpalo::Bump; +use liquid::{Parser, Template}; +use serde_json::{Map, Value}; + +use crate::prompt::ParseableDocument; +use crate::update::new::document::Document; + +mod injectable_value; + +pub use injectable_value::InjectableValue; + +/// Represents a JSON [`Value`] where each string is rendered as a [`Template`]. +pub struct JsonTemplate { + value: Value, + templates: Vec, + bump: Bump, +} + +struct TemplateAtPath { + template: Template, + path: ValuePath, +} + +/// Error that can occur either when parsing the templates in the value, or when trying to render them. +pub struct Error { + template_error: liquid::Error, + path: ValuePath, +} + +impl Error { + /// Produces an error message when the error happened at rendering time. + pub fn rendering_error(&self, root: &str) -> String { + format!( + "in `{}`, error while rendering template: {}", + path_with_root(root, self.path.iter()), + &self.template_error + ) + } + + /// Produces an error message when the error happened at parsing time. + pub fn parsing(&self, root: &str) -> String { + format!( + "in `{}`, error while parsing template: {}", + path_with_root(root, self.path.iter()), + &self.template_error + ) + } +} + +impl JsonTemplate { + /// Creates a new `JsonTemplate` by parsing all strings inside the value as templates. + /// + /// # Error + /// + /// - If any of the strings contains a template that cannot be parsed. + pub fn new(value: Value) -> Result { + let templates = build_templates(&value)?; + let bump = Bump::new(); + Ok(Self { value, templates, bump }) + } + + /// Renders this value by replacing all its strings with the rendered version of the template their represent from the contents of the given document. + /// + /// # Error + /// + /// - If any of the strings contains a template that cannot be rendered with the given document. + pub fn render_document<'a, D: Document<'a> + std::fmt::Debug>( + &'a self, + document: D, + ) -> Result { + let mut rendered = self.value.clone(); + let document = ParseableDocument::new(document, &self.bump); + for TemplateAtPath { template, path } in &self.templates { + let injected_value = + template.render(&document).map_err(|err| error_with_path(err, path.clone()))?; + inject_value(&mut rendered, path, Value::String(injected_value)); + } + Ok(rendered) + } + + /// Renders this value by replacing all its strings with the rendered version of the template their represent from the contents of the search query. + /// + /// # Error + /// + /// - If any of the strings contains a template that cannot be rendered from the contents of the search query + pub fn render_search(&self, q: Option, media: Value) -> Result { + let mut rendered = self.value.clone(); + let search_data = liquid::object!({ + "q": q, + "media": media + }); + for TemplateAtPath { template, path } in &self.templates { + let injected_value = + template.render(&search_data).map_err(|err| error_with_path(err, path.clone()))?; + inject_value(&mut rendered, path, Value::String(injected_value)); + } + Ok(rendered) + } +} + +fn build_templates(value: &Value) -> Result, Error> { + let mut current_path = ValuePath::new(); + let mut templates = Vec::new(); + let compiler = liquid::ParserBuilder::with_stdlib().build().unwrap(); + parse_value(value, &mut current_path, &mut templates, &compiler)?; + Ok(templates) +} + +fn error_with_path(template_error: liquid::Error, path: ValuePath) -> Error { + Error { template_error, path } +} + +fn parse_value( + value: &Value, + current_path: &mut ValuePath, + templates: &mut Vec, + compiler: &Parser, +) -> Result<(), Error> { + match value { + Value::String(template) => { + let template = compiler + .parse(template) + .map_err(|err| error_with_path(err, current_path.clone()))?; + templates.push(TemplateAtPath { template, path: current_path.clone() }); + } + Value::Array(values) => { + parse_array(values, current_path, templates, compiler)?; + } + Value::Object(map) => { + parse_object(map, current_path, templates, compiler)?; + } + _ => {} + } + Ok(()) +} + +fn parse_object( + map: &Map, + current_path: &mut ValuePath, + templates: &mut Vec, + compiler: &Parser, +) -> Result<(), Error> { + for (key, value) in map { + current_path.push(PathComponent::MapKey(key.clone())); + parse_value(value, current_path, templates, compiler)?; + current_path.pop(); + } + Ok(()) +} + +fn parse_array( + values: &[Value], + current_path: &mut ValuePath, + templates: &mut Vec, + compiler: &Parser, +) -> Result<(), Error> { + for (index, value) in values.iter().enumerate() { + current_path.push(PathComponent::ArrayIndex(index)); + parse_value(value, current_path, templates, compiler)?; + current_path.pop(); + } + Ok(()) +} + +/// A list of [`PathComponent`]s describing a path to a value inside a JSON value. +/// +/// The empty list refers to the root value. +pub type ValuePath = Vec; + +/// Component of a path to a Value +#[derive(Debug, Clone)] +pub enum PathComponent { + /// A key inside of an object + MapKey(String), + /// An index inside of an array + ArrayIndex(usize), +} + +impl PartialEq for PathComponent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::MapKey(l0), Self::MapKey(r0)) => l0 == r0, + (Self::ArrayIndex(l0), Self::ArrayIndex(r0)) => l0 == r0, + _ => false, + } + } +} + +impl Eq for PathComponent {} + +/// Builds a string representation of a path, preprending the name of the root value. +pub fn path_with_root<'a>( + root: &str, + path: impl IntoIterator + 'a, +) -> String { + use std::fmt::Write as _; + let mut res = format!("`{root}"); + for component in path.into_iter() { + match component { + PathComponent::MapKey(key) => { + let _ = write!(&mut res, ".{key}"); + } + PathComponent::ArrayIndex(index) => { + let _ = write!(&mut res, "[{index}]"); + } + } + } + res.push('`'); + res +} + +/// Modifies `rendered` to replace the sub-value at the `injection_path` location by the `injected_value`. +/// +/// # Panics +/// +/// - if the provided `injection_path` cannot be traversed in `rendered`. +pub fn inject_value( + rendered: &mut Value, + injection_path: &Vec, + injected_value: Value, +) { + let mut current_value = rendered; + for injection_component in injection_path { + current_value = match injection_component { + PathComponent::MapKey(key) => current_value.get_mut(key).unwrap(), + PathComponent::ArrayIndex(index) => current_value.get_mut(index).unwrap(), + } + } + *current_value = injected_value; +} + +fn format_value(value: &Value) -> String { + match value { + Value::Array(array) => format!("an array of size {}", array.len()), + Value::Object(object) => { + format!("an object with {} field(s)", object.len()) + } + value => value.to_string(), + } +} diff --git a/crates/milli/src/vector/rest.rs b/crates/milli/src/vector/rest.rs index b87ac9f77..9e7e99cf1 100644 --- a/crates/milli/src/vector/rest.rs +++ b/crates/milli/src/vector/rest.rs @@ -8,7 +8,7 @@ use rayon::slice::ParallelSlice as _; use serde::{Deserialize, Serialize}; use super::error::EmbedErrorKind; -use super::json_template::ValueTemplate; +use super::json_template::InjectableValue; use super::{ DistributionShift, EmbedError, Embedding, EmbeddingCache, NewEmbedderError, REQUEST_PARALLELISM, }; @@ -417,12 +417,13 @@ pub(super) const REPEAT_PLACEHOLDER: &str = "{{..}}"; #[derive(Debug)] pub struct Request { - template: ValueTemplate, + template: InjectableValue, } impl Request { pub fn new(template: serde_json::Value) -> Result { - let template = match ValueTemplate::new(template, REQUEST_PLACEHOLDER, REPEAT_PLACEHOLDER) { + let template = match InjectableValue::new(template, REQUEST_PLACEHOLDER, REPEAT_PLACEHOLDER) + { Ok(template) => template, Err(error) => { let message = @@ -452,20 +453,20 @@ impl Request { #[derive(Debug)] pub struct Response { - template: ValueTemplate, + template: InjectableValue, } impl Response { pub fn new(template: serde_json::Value, request: &Request) -> Result { - let template = match ValueTemplate::new(template, RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER) - { - Ok(template) => template, - Err(error) => { - let message = - error.error_message("response", RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER); - return Err(NewEmbedderError::rest_could_not_parse_template(message)); - } - }; + let template = + match InjectableValue::new(template, RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER) { + Ok(template) => template, + Err(error) => { + let message = + error.error_message("response", RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER); + return Err(NewEmbedderError::rest_could_not_parse_template(message)); + } + }; match (template.has_array_value(), request.template.has_array_value()) { (true, true) | (false, false) => Ok(Self {template}),