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
This commit is contained in:
Louis Dureuil 2025-05-27 18:22:28 +02:00
parent 97aeb6db4d
commit f26ab53941
No known key found for this signature in database
4 changed files with 329 additions and 135 deletions

View File

@ -10,7 +10,7 @@ use std::fmt::Debug;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use bumpalo::Bump; use bumpalo::Bump;
use document::ParseableDocument; pub(crate) use document::ParseableDocument;
use error::{NewPromptError, RenderPromptError}; use error::{NewPromptError, RenderPromptError};
use fields::{BorrowedFields, OwnedFields}; use fields::{BorrowedFields, OwnedFields};

View File

@ -1,20 +1,17 @@
//! Module to manipulate JSON templates. //! Module to manipulate JSON values containing placeholder strings.
//! //!
//! This module allows two main operations: //! This module allows two main operations:
//! 1. Render JSON values from a template and a context value. //! 1. Render JSON values from a template value containing placeholders and a value to inject.
//! 2. Retrieve data from a template and JSON values. //! 2. Extract data from a template value containing placeholders and a concrete JSON value that fits the template value.
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(missing_docs)]
use serde::Deserialize; use serde::Deserialize;
use serde_json::{Map, Value}; use serde_json::{Map, Value};
type ValuePath = Vec<PathComponent>; use super::{format_value, inject_value, path_with_root, PathComponent, ValuePath};
/// Encapsulates a JSON template and allows injecting and extracting values from it. /// Encapsulates a JSON template and allows injecting and extracting values from it.
#[derive(Debug)] #[derive(Debug)]
pub struct ValueTemplate { pub struct InjectableValue {
template: Value, template: Value,
value_kind: ValueKind, value_kind: ValueKind,
} }
@ -32,34 +29,13 @@ struct ArrayPath {
value_path_in_array: ValuePath, value_path_in_array: ValuePath,
} }
/// Component of a path to a Value /// Error that occurs when no value was provided to a template for injection.
#[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.
#[derive(Debug)] #[derive(Debug)]
pub struct MissingValue; 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)] #[derive(Debug)]
pub enum TemplateParsingError { pub enum InjectableParsingError {
/// A repeat string appears inside a repeated value /// A repeat string appears inside a repeated value
NestedRepeatString(ValuePath), NestedRepeatString(ValuePath),
/// A repeat string appears outside of an array /// 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 /// 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 { pub fn error_message(&self, root: &str, placeholder: &str, repeat: &str) -> String {
match self { match self {
TemplateParsingError::NestedRepeatString(path) => { InjectableParsingError::NestedRepeatString(path) => {
format!( format!(
r#"in {}: "{repeat}" appears nested inside of a value that is itself repeated"#, r#"in {}: "{repeat}" appears nested inside of a value that is itself repeated"#,
path_with_root(root, path) path_with_root(root, path)
) )
} }
TemplateParsingError::RepeatStringNotInArray(path) => format!( InjectableParsingError::RepeatStringNotInArray(path) => format!(
r#"in {}: "{repeat}" appears outside of an array"#, r#"in {}: "{repeat}" appears outside of an array"#,
path_with_root(root, path) 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}"#, r#"in {}: "{repeat}" expected at position #1, but found at position #{index}"#,
path_with_root(root, path) path_with_root(root, path)
), ),
TemplateParsingError::MissingPlaceholderInRepeatedValue(path) => format!( InjectableParsingError::MissingPlaceholderInRepeatedValue(path) => format!(
r#"in {}: Expected "{placeholder}" inside of the repeated value"#, r#"in {}: Expected "{placeholder}" inside of the repeated value"#,
path_with_root(root, path) 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 {}"#, r#"in {}: Found "{repeat}", but it was already present in {}"#,
path_with_root(root, current), path_with_root(root, current),
path_with_root(root, previous) 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 {}"#, r#"in {}: Found "{placeholder}", but it was already present in {}"#,
path_with_root(root, current), path_with_root(root, current),
path_with_root(root, previous) path_with_root(root, previous)
), ),
TemplateParsingError::MissingPlaceholderString => { InjectableParsingError::MissingPlaceholderString => {
format!(r#"in `{root}`: "{placeholder}" not found"#) format!(r#"in `{root}`: "{placeholder}" not found"#)
} }
TemplateParsingError::BothArrayAndSingle { InjectableParsingError::BothArrayAndSingle {
single_path, single_path,
path_to_array, path_to_array,
array_to_placeholder, array_to_placeholder,
@ -140,41 +116,41 @@ impl TemplateParsingError {
fn prepend_path(self, mut prepended_path: ValuePath) -> Self { fn prepend_path(self, mut prepended_path: ValuePath) -> Self {
match self { match self {
TemplateParsingError::NestedRepeatString(mut path) => { InjectableParsingError::NestedRepeatString(mut path) => {
prepended_path.append(&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); 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); 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); 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 = let older_prepended_path =
prepended_path.iter().cloned().chain(older_path).collect(); prepended_path.iter().cloned().chain(older_path).collect();
prepended_path.append(&mut path); 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 = let older_prepended_path =
prepended_path.iter().cloned().chain(older_path).collect(); prepended_path.iter().cloned().chain(older_path).collect();
prepended_path.append(&mut path); prepended_path.append(&mut path);
TemplateParsingError::MultiplePlaceholderString( InjectableParsingError::MultiplePlaceholderString(
prepended_path, prepended_path,
older_prepended_path, older_prepended_path,
) )
} }
TemplateParsingError::MissingPlaceholderString => { InjectableParsingError::MissingPlaceholderString => {
TemplateParsingError::MissingPlaceholderString InjectableParsingError::MissingPlaceholderString
} }
TemplateParsingError::BothArrayAndSingle { InjectableParsingError::BothArrayAndSingle {
single_path, single_path,
mut path_to_array, mut path_to_array,
array_to_placeholder, array_to_placeholder,
@ -184,7 +160,7 @@ impl TemplateParsingError {
prepended_path.iter().cloned().chain(single_path).collect(); prepended_path.iter().cloned().chain(single_path).collect();
prepended_path.append(&mut path_to_array); 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 // 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, single_path: single_prepended_path,
path_to_array: prepended_path, path_to_array: prepended_path,
array_to_placeholder, 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)] #[derive(Debug)]
pub struct ExtractionError { pub struct ExtractionError {
/// The cause of the failure /// The cause of the failure
@ -336,27 +312,6 @@ enum LastNamedObject<'a> {
NestedArrayInsideObject { object_name: &'a str, index: usize, nesting_level: usize }, 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<Item = &'a PathComponent> + '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 /// Context where an extraction failure happened
/// ///
/// The operation that failed /// The operation that failed
@ -405,7 +360,7 @@ enum ArrayParsingContext<'a> {
NotNested(&'a mut Option<ArrayPath>), NotNested(&'a mut Option<ArrayPath>),
} }
impl ValueTemplate { impl InjectableValue {
/// Prepare a template for injection or extraction. /// Prepare a template for injection or extraction.
/// ///
/// # Parameters /// # Parameters
@ -419,12 +374,12 @@ impl ValueTemplate {
/// ///
/// # Errors /// # Errors
/// ///
/// - [`TemplateParsingError`]: refer to the documentation of this type /// - [`InjectableParsingError`]: refer to the documentation of this type
pub fn new( pub fn new(
template: Value, template: Value,
placeholder_string: &str, placeholder_string: &str,
repeat_string: &str, repeat_string: &str,
) -> Result<Self, TemplateParsingError> { ) -> Result<Self, InjectableParsingError> {
let mut value_path = None; let mut value_path = None;
let mut array_path = None; let mut array_path = None;
let mut current_path = Vec::new(); let mut current_path = Vec::new();
@ -438,11 +393,11 @@ impl ValueTemplate {
)?; )?;
let value_kind = match (array_path, value_path) { 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), (None, Some(value_path)) => ValueKind::Single(value_path),
(Some(array_path), None) => ValueKind::Array(array_path), (Some(array_path), None) => ValueKind::Array(array_path),
(Some(array_path), Some(value_path)) => { (Some(array_path), Some(value_path)) => {
return Err(TemplateParsingError::BothArrayAndSingle { return Err(InjectableParsingError::BothArrayAndSingle {
single_path: value_path, single_path: value_path,
path_to_array: array_path.path_to_array, path_to_array: array_path.path_to_array,
array_to_placeholder: array_path.value_path_in_array, array_to_placeholder: array_path.value_path_in_array,
@ -564,29 +519,29 @@ impl ValueTemplate {
value_path: &mut Option<ValuePath>, value_path: &mut Option<ValuePath>,
mut array_path: &mut ArrayParsingContext, mut array_path: &mut ArrayParsingContext,
current_path: &mut ValuePath, current_path: &mut ValuePath,
) -> Result<(), TemplateParsingError> { ) -> Result<(), InjectableParsingError> {
// two modes for parsing array. // two modes for parsing array.
match array { match array {
// 1. array contains a repeat string in second position // 1. array contains a repeat string in second position
[first, second, rest @ ..] if second == repeat_string => { [first, second, rest @ ..] if second == repeat_string => {
let ArrayParsingContext::NotNested(array_path) = &mut array_path else { 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 { if let Some(array_path) = array_path {
return Err(TemplateParsingError::MultipleRepeatString( return Err(InjectableParsingError::MultipleRepeatString(
current_path.clone(), current_path.clone(),
array_path.path_to_array.clone(), array_path.path_to_array.clone(),
)); ));
} }
if first == repeat_string { if first == repeat_string {
return Err(TemplateParsingError::BadIndexForRepeatString( return Err(InjectableParsingError::BadIndexForRepeatString(
current_path.clone(), current_path.clone(),
0, 0,
)); ));
} }
if let Some(position) = rest.iter().position(|value| value == repeat_string) { if let Some(position) = rest.iter().position(|value| value == repeat_string) {
let position = position + 2; let position = position + 2;
return Err(TemplateParsingError::BadIndexForRepeatString( return Err(InjectableParsingError::BadIndexForRepeatString(
current_path.clone(), current_path.clone(),
position, position,
)); ));
@ -609,7 +564,9 @@ impl ValueTemplate {
value_path.ok_or_else(|| { value_path.ok_or_else(|| {
let mut repeated_value_path = current_path.clone(); let mut repeated_value_path = current_path.clone();
repeated_value_path.push(PathComponent::ArrayIndex(0)); repeated_value_path.push(PathComponent::ArrayIndex(0));
TemplateParsingError::MissingPlaceholderInRepeatedValue(repeated_value_path) InjectableParsingError::MissingPlaceholderInRepeatedValue(
repeated_value_path,
)
})? })?
}; };
**array_path = Some(ArrayPath { **array_path = Some(ArrayPath {
@ -621,7 +578,7 @@ impl ValueTemplate {
// 2. array does not contain a repeat string // 2. array does not contain a repeat string
array => { array => {
if let Some(position) = array.iter().position(|value| value == repeat_string) { if let Some(position) = array.iter().position(|value| value == repeat_string) {
return Err(TemplateParsingError::BadIndexForRepeatString( return Err(InjectableParsingError::BadIndexForRepeatString(
current_path.clone(), current_path.clone(),
position, position,
)); ));
@ -650,7 +607,7 @@ impl ValueTemplate {
value_path: &mut Option<ValuePath>, value_path: &mut Option<ValuePath>,
array_path: &mut ArrayParsingContext, array_path: &mut ArrayParsingContext,
current_path: &mut ValuePath, current_path: &mut ValuePath,
) -> Result<(), TemplateParsingError> { ) -> Result<(), InjectableParsingError> {
for (key, value) in object.iter() { for (key, value) in object.iter() {
current_path.push(PathComponent::MapKey(key.to_owned())); current_path.push(PathComponent::MapKey(key.to_owned()));
Self::parse_value( Self::parse_value(
@ -673,12 +630,12 @@ impl ValueTemplate {
value_path: &mut Option<ValuePath>, value_path: &mut Option<ValuePath>,
array_path: &mut ArrayParsingContext, array_path: &mut ArrayParsingContext,
current_path: &mut ValuePath, current_path: &mut ValuePath,
) -> Result<(), TemplateParsingError> { ) -> Result<(), InjectableParsingError> {
match value { match value {
Value::String(str) => { Value::String(str) => {
if placeholder_string == str { if placeholder_string == str {
if let Some(value_path) = value_path { if let Some(value_path) = value_path {
return Err(TemplateParsingError::MultiplePlaceholderString( return Err(InjectableParsingError::MultiplePlaceholderString(
current_path.clone(), current_path.clone(),
value_path.clone(), value_path.clone(),
)); ));
@ -687,7 +644,9 @@ impl ValueTemplate {
*value_path = Some(current_path.clone()); *value_path = Some(current_path.clone());
} }
if repeat_string == str { if repeat_string == str {
return Err(TemplateParsingError::RepeatStringNotInArray(current_path.clone())); return Err(InjectableParsingError::RepeatStringNotInArray(
current_path.clone(),
));
} }
} }
Value::Null | Value::Bool(_) | Value::Number(_) => {} Value::Null | Value::Bool(_) | Value::Number(_) => {}
@ -712,27 +671,6 @@ impl ValueTemplate {
} }
} }
fn inject_value(rendered: &mut Value, injection_path: &Vec<PathComponent>, 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<T>( fn extract_value<T>(
extraction_path: &[PathComponent], extraction_path: &[PathComponent],
initial_value: &mut Value, initial_value: &mut Value,
@ -838,10 +776,10 @@ impl<T> ExtractionResultErrorContext<T> for Result<T, ExtractionErrorKind> {
mod test { mod test {
use serde_json::{json, Value}; use serde_json::{json, Value};
use super::{PathComponent, TemplateParsingError, ValueTemplate}; use super::{InjectableParsingError, InjectableValue, PathComponent};
fn new_template(template: Value) -> Result<ValueTemplate, TemplateParsingError> { fn new_template(template: Value) -> Result<InjectableValue, InjectableParsingError> {
ValueTemplate::new(template, "{{text}}", "{{..}}") InjectableValue::new(template, "{{text}}", "{{..}}")
} }
#[test] #[test]
@ -853,7 +791,7 @@ mod test {
}); });
let error = new_template(template.clone()).unwrap_err(); let error = new_template(template.clone()).unwrap_err();
assert!(matches!(error, TemplateParsingError::MissingPlaceholderString)) assert!(matches!(error, InjectableParsingError::MissingPlaceholderString))
} }
#[test] #[test]
@ -887,7 +825,7 @@ mod test {
}); });
match new_template(template.clone()) { match new_template(template.clone()) {
Err(TemplateParsingError::MultiplePlaceholderString(left, right)) => { Err(InjectableParsingError::MultiplePlaceholderString(left, right)) => {
assert_eq!( assert_eq!(
left, left,
vec![PathComponent::MapKey("titi".into()), PathComponent::ArrayIndex(3)] vec![PathComponent::MapKey("titi".into()), PathComponent::ArrayIndex(3)]

View File

@ -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<TemplateAtPath>,
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<Self, Error> {
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<Value, Error> {
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<String>, media: Value) -> Result<Value, Error> {
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<Vec<TemplateAtPath>, 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<TemplateAtPath>,
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<String, Value>,
current_path: &mut ValuePath,
templates: &mut Vec<TemplateAtPath>,
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<TemplateAtPath>,
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<PathComponent>;
/// 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<Item = &'a PathComponent> + '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<PathComponent>,
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(),
}
}

View File

@ -8,7 +8,7 @@ use rayon::slice::ParallelSlice as _;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::error::EmbedErrorKind; use super::error::EmbedErrorKind;
use super::json_template::ValueTemplate; use super::json_template::InjectableValue;
use super::{ use super::{
DistributionShift, EmbedError, Embedding, EmbeddingCache, NewEmbedderError, REQUEST_PARALLELISM, DistributionShift, EmbedError, Embedding, EmbeddingCache, NewEmbedderError, REQUEST_PARALLELISM,
}; };
@ -417,12 +417,13 @@ pub(super) const REPEAT_PLACEHOLDER: &str = "{{..}}";
#[derive(Debug)] #[derive(Debug)]
pub struct Request { pub struct Request {
template: ValueTemplate, template: InjectableValue,
} }
impl Request { impl Request {
pub fn new(template: serde_json::Value) -> Result<Self, NewEmbedderError> { pub fn new(template: serde_json::Value) -> Result<Self, NewEmbedderError> {
let template = match ValueTemplate::new(template, REQUEST_PLACEHOLDER, REPEAT_PLACEHOLDER) { let template = match InjectableValue::new(template, REQUEST_PLACEHOLDER, REPEAT_PLACEHOLDER)
{
Ok(template) => template, Ok(template) => template,
Err(error) => { Err(error) => {
let message = let message =
@ -452,20 +453,20 @@ impl Request {
#[derive(Debug)] #[derive(Debug)]
pub struct Response { pub struct Response {
template: ValueTemplate, template: InjectableValue,
} }
impl Response { impl Response {
pub fn new(template: serde_json::Value, request: &Request) -> Result<Self, NewEmbedderError> { pub fn new(template: serde_json::Value, request: &Request) -> Result<Self, NewEmbedderError> {
let template = match ValueTemplate::new(template, RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER) let template =
{ match InjectableValue::new(template, RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER) {
Ok(template) => template, Ok(template) => template,
Err(error) => { Err(error) => {
let message = let message =
error.error_message("response", RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER); error.error_message("response", RESPONSE_PLACEHOLDER, REPEAT_PLACEHOLDER);
return Err(NewEmbedderError::rest_could_not_parse_template(message)); return Err(NewEmbedderError::rest_could_not_parse_template(message));
} }
}; };
match (template.has_array_value(), request.template.has_array_value()) { match (template.has_array_value(), request.template.has_array_value()) {
(true, true) | (false, false) => Ok(Self {template}), (true, true) | (false, false) => Ok(Self {template}),