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::index_uid::IndexUid;
use meilisearch_types::keys::actions;
use meilisearch_types::milli::vector::json_template::{self, JsonTemplate};
use meilisearch_types::serde_cs::vec::CS;
use meilisearch_types::{heed, milli, Index};
use serde::Serialize;
@ -165,6 +166,8 @@ enum RenderError {
DocumentNotFound(String),
BothInlineDocAndDocId,
TemplateParsing(json_template::Error),
TemplateRendering(json_template::Error),
}
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."),
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)]
@ -471,5 +489,5 @@ pub struct RenderQueryInput {
#[derive(Debug, Clone, Serialize, PartialEq, ToSchema)]
pub struct RenderResult {
template: serde_json::Value,
rendered: String,
rendered: serde_json::Value,
}

View File

@ -14,7 +14,8 @@
#![warn(missing_docs)]
use bumpalo::Bump;
use liquid::{Parser, Template};
use liquid::{Object, Parser, Template};
use serde::Serialize;
use serde_json::{Map, Value};
use crate::prompt::ParseableDocument;
@ -61,15 +62,28 @@ pub struct Error {
impl Error {
/// Produces an error message when the error happened at rendering time.
pub fn rendering_error(&self, root: &str) -> String {
if self.path.is_empty() {
format!(
"error while rendering template: {}",
&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.
pub fn parsing(&self, root: &str) -> String {
pub fn parsing_error(&self, root: &str) -> String {
if self.path.is_empty() {
format!(
"error while parsing template: {}",
&self.template_error
)
} else {
format!(
"in `{}`, error while parsing template: {}",
path_with_root(root, self.path.iter()),
@ -77,6 +91,7 @@ impl Error {
)
}
}
}
impl JsonTemplate {
/// Creates a new `JsonTemplate` by parsing all strings inside the value as templates.
@ -134,6 +149,17 @@ impl JsonTemplate {
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
pub fn template(&self) -> &Value {
&self.value

View File

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