Template rendering

This commit is contained in:
Mubelotix
2025-07-17 08:44:04 +02:00
parent f349ba53a0
commit 2d2de778a7
3 changed files with 60 additions and 16 deletions

View File

@ -13,6 +13,7 @@ use meilisearch_types::error::ResponseError;
use meilisearch_types::error::{Code}; use meilisearch_types::error::{Code};
use meilisearch_types::index_uid::IndexUid; use meilisearch_types::index_uid::IndexUid;
use meilisearch_types::keys::actions; use meilisearch_types::keys::actions;
use meilisearch_types::milli::vector::json_template::{self, JsonTemplate};
use meilisearch_types::serde_cs::vec::CS; use meilisearch_types::serde_cs::vec::CS;
use meilisearch_types::{heed, milli, Index}; use meilisearch_types::{heed, milli, Index};
use serde::Serialize; use serde::Serialize;
@ -165,6 +166,8 @@ enum RenderError {
DocumentNotFound(String), DocumentNotFound(String),
BothInlineDocAndDocId, BothInlineDocAndDocId,
TemplateParsing(json_template::Error),
TemplateRendering(json_template::Error),
} }
impl From<heed::Error> for RenderError { impl From<heed::Error> for RenderError {
@ -282,6 +285,14 @@ impl From<RenderError> for ResponseError {
String::from("A document id was provided but adding it to the input would overwrite the `doc` field that you already defined inline."), String::from("A document id was provided but adding it to the input would overwrite the `doc` field that you already defined inline."),
Code::InvalidRenderInput, Code::InvalidRenderInput,
), ),
TemplateParsing(err) => ResponseError::from_msg(
format!("Error parsing template: {}", err.parsing_error("input")),
Code::InvalidRenderTemplate,
),
TemplateRendering(err) => ResponseError::from_msg(
format!("Error rendering template: {}", err.rendering_error("input")),
Code::InvalidRenderTemplate,
),
} }
} }
} }
@ -438,7 +449,14 @@ async fn render(index: Index, query: RenderQuery) -> Result<RenderResult, Render
} }
} }
Ok(RenderResult { template, rendered: String::from("TODO: Implement render logic here") }) let json_template = JsonTemplate::new(template.clone())
.map_err(TemplateParsing)?;
let rendered = json_template
.render_serializable(&media)
.map_err(TemplateRendering)?;
Ok(RenderResult { template, rendered })
} }
#[derive(Debug, Clone, PartialEq, Deserr, ToSchema)] #[derive(Debug, Clone, PartialEq, Deserr, ToSchema)]
@ -471,5 +489,5 @@ pub struct RenderQueryInput {
#[derive(Debug, Clone, Serialize, PartialEq, ToSchema)] #[derive(Debug, Clone, Serialize, PartialEq, ToSchema)]
pub struct RenderResult { pub struct RenderResult {
template: serde_json::Value, template: serde_json::Value,
rendered: String, rendered: serde_json::Value,
} }

View File

@ -14,7 +14,8 @@
#![warn(missing_docs)] #![warn(missing_docs)]
use bumpalo::Bump; use bumpalo::Bump;
use liquid::{Parser, Template}; use liquid::{Object, Parser, Template};
use serde::Serialize;
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use crate::prompt::ParseableDocument; use crate::prompt::ParseableDocument;
@ -61,20 +62,34 @@ pub struct Error {
impl Error { impl Error {
/// Produces an error message when the error happened at rendering time. /// Produces an error message when the error happened at rendering time.
pub fn rendering_error(&self, root: &str) -> String { pub fn rendering_error(&self, root: &str) -> String {
format!( if self.path.is_empty() {
"in `{}`, error while rendering template: {}", format!(
path_with_root(root, self.path.iter()), "error while rendering template: {}",
&self.template_error &self.template_error
) )
} else {
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. /// Produces an error message when the error happened at parsing time.
pub fn parsing(&self, root: &str) -> String { pub fn parsing_error(&self, root: &str) -> String {
format!( if self.path.is_empty() {
"in `{}`, error while parsing template: {}", format!(
path_with_root(root, self.path.iter()), "error while parsing template: {}",
&self.template_error &self.template_error
) )
} else {
format!(
"in `{}`, error while parsing template: {}",
path_with_root(root, self.path.iter()),
&self.template_error
)
}
} }
} }
@ -134,6 +149,17 @@ impl JsonTemplate {
self.render(&search_data) self.render(&search_data)
} }
/// Renders any serializable value by converting it to a liquid object and rendering it with the template.
/// If its a map, values inside can be accessed directly by their keys.
pub fn render_serializable<T: Serialize>(&self, object: &T) -> Result<Value, Error> {
let object = liquid::to_object(object)
.map_err(|err| Error {
template_error: err,
path: ValuePath::new(),
})?;
self.render(&object)
}
/// The JSON value representing the underlying template /// The JSON value representing the underlying template
pub fn template(&self) -> &Value { pub fn template(&self) -> &Value {
&self.value &self.value

View File

@ -113,7 +113,7 @@ impl RequestData {
for (name, value) in indexing_fragments { for (name, value) in indexing_fragments {
JsonTemplate::new(value).map_err(|error| { JsonTemplate::new(value).map_err(|error| {
NewEmbedderError::rest_could_not_parse_template( NewEmbedderError::rest_could_not_parse_template(
error.parsing(&format!(".indexingFragments.{name}")), error.parsing_error(&format!(".indexingFragments.{name}")),
) )
})?; })?;
} }
@ -623,7 +623,7 @@ impl RequestFromFragments {
.map(|(name, value)| { .map(|(name, value)| {
let json_template = JsonTemplate::new(value).map_err(|error| { let json_template = JsonTemplate::new(value).map_err(|error| {
NewEmbedderError::rest_could_not_parse_template( NewEmbedderError::rest_could_not_parse_template(
error.parsing(&format!(".searchFragments.{name}")), error.parsing_error(&format!(".searchFragments.{name}")),
) )
})?; })?;
Ok((name, json_template)) Ok((name, json_template))