mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-06-06 20:25:40 +00:00
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:
parent
97aeb6db4d
commit
f26ab53941
@ -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};
|
||||||
|
|
||||||
|
@ -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)]
|
255
crates/milli/src/vector/json_template/mod.rs
Normal file
255
crates/milli/src/vector/json_template/mod.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
@ -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,13 +453,13 @@ 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 =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user