diff --git a/crates/meilisearch/src/routes/indexes/render.rs b/crates/meilisearch/src/routes/indexes/render.rs index d5c284f42..66e9cea28 100644 --- a/crates/meilisearch/src/routes/indexes/render.rs +++ b/crates/meilisearch/src/routes/indexes/render.rs @@ -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 for RenderError { @@ -282,6 +285,14 @@ impl From 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 String { - format!( - "in `{}`, error while rendering template: {}", - path_with_root(root, self.path.iter()), - &self.template_error - ) + 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 { - format!( - "in `{}`, error while parsing template: {}", - path_with_root(root, self.path.iter()), - &self.template_error - ) + 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()), + &self.template_error + ) + } } } @@ -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(&self, object: &T) -> Result { + 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 diff --git a/crates/milli/src/vector/rest.rs b/crates/milli/src/vector/rest.rs index 7a16f1a1e..f57d4b6c0 100644 --- a/crates/milli/src/vector/rest.rs +++ b/crates/milli/src/vector/rest.rs @@ -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))