mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-06-06 04:05:37 +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 bumpalo::Bump;
|
||||
use document::ParseableDocument;
|
||||
pub(crate) use document::ParseableDocument;
|
||||
use error::{NewPromptError, RenderPromptError};
|
||||
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:
|
||||
//! 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<PathComponent>;
|
||||
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<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
|
||||
///
|
||||
/// The operation that failed
|
||||
@ -405,7 +360,7 @@ enum ArrayParsingContext<'a> {
|
||||
NotNested(&'a mut Option<ArrayPath>),
|
||||
}
|
||||
|
||||
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<Self, TemplateParsingError> {
|
||||
) -> Result<Self, InjectableParsingError> {
|
||||
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<ValuePath>,
|
||||
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<ValuePath>,
|
||||
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<ValuePath>,
|
||||
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<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>(
|
||||
extraction_path: &[PathComponent],
|
||||
initial_value: &mut Value,
|
||||
@ -838,10 +776,10 @@ impl<T> ExtractionResultErrorContext<T> for Result<T, ExtractionErrorKind> {
|
||||
mod test {
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::{PathComponent, TemplateParsingError, ValueTemplate};
|
||||
use super::{InjectableParsingError, InjectableValue, PathComponent};
|
||||
|
||||
fn new_template(template: Value) -> Result<ValueTemplate, TemplateParsingError> {
|
||||
ValueTemplate::new(template, "{{text}}", "{{..}}")
|
||||
fn new_template(template: Value) -> Result<InjectableValue, InjectableParsingError> {
|
||||
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)]
|
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 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<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,
|
||||
Err(error) => {
|
||||
let message =
|
||||
@ -452,13 +453,13 @@ impl Request {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
template: ValueTemplate,
|
||||
template: InjectableValue,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
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,
|
||||
Err(error) => {
|
||||
let message =
|
||||
|
Loading…
x
Reference in New Issue
Block a user