Add columns info in errors

This commit is contained in:
Mubelotix
2025-08-01 14:53:41 +02:00
parent 8fe4d33b5a
commit a20f353054
2 changed files with 51 additions and 31 deletions

View File

@ -202,6 +202,18 @@ use RenderError::*;
impl From<RenderError<'_>> for ResponseError { impl From<RenderError<'_>> for ResponseError {
fn from(error: RenderError) -> Self { fn from(error: RenderError) -> Self {
fn format_span(span: &Span<'_>) -> String {
let base_column = span.get_utf8_column();
let size = span.fragment().chars().count();
format!("`{}` (cols {}:{})", span.fragment(), base_column, base_column + size)
}
fn format_token(token: &Token<'_>) -> String {
let base_column = token.original_span().get_utf8_column();
let size = token.original_span().fragment().chars().count();
format!("`{}` (cols {}:{})", token.value(), base_column, base_column + size)
}
match error { match error {
MultipleTemplates => ResponseError::from_msg( MultipleTemplates => ResponseError::from_msg(
String::from("Cannot provide both an inline template and a template ID."), String::from("Cannot provide both an inline template and a template ID."),
@ -216,7 +228,7 @@ impl From<RenderError<'_>> for ResponseError {
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
UnknownTemplateRoot(root) => ResponseError::from_msg( UnknownTemplateRoot(root) => ResponseError::from_msg(
format!("Template ID must start with `embedders` or `chatCompletions`, but found `{root}`."), format!("Template ID must start with `embedders` or `chatCompletions`, but found {}.", format_token(&root)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
MissingEmbedderName { mut available } => { MissingEmbedderName { mut available } => {
@ -230,26 +242,29 @@ impl From<RenderError<'_>> for ResponseError {
EmbedderDoesNotExist { embedder, mut available } => { EmbedderDoesNotExist { embedder, mut available } => {
available.sort_unstable(); available.sort_unstable();
ResponseError::from_msg( ResponseError::from_msg(
format!("Embedder `{embedder}` does not exist.\n Hint: Available embedders are {}.", format!("Embedder {} does not exist.\n Hint: Available embedders are {}.",
format_token(&embedder),
available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")), available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
) )
}, },
EmbedderUsesFragments { embedder } => ResponseError::from_msg( EmbedderUsesFragments { embedder } => ResponseError::from_msg(
format!("Requested document template for embedder `{embedder}` but it uses fragments.\n Hint: Use `indexingFragments` or `searchFragments` instead."), format!("Requested document template for embedder {} but it uses fragments.\n Hint: Use `indexingFragments` or `searchFragments` instead.", format_token(&embedder)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
MissingTemplateAfterEmbedder { embedder, mut indexing, mut search } => { MissingTemplateAfterEmbedder { embedder, mut indexing, mut search } => {
if indexing.is_empty() && search.is_empty() { if indexing.is_empty() && search.is_empty() {
ResponseError::from_msg( ResponseError::from_msg(
format!("Missing template id after embedder `{embedder}`.\n Hint: Available template: `documentTemplate`."), format!("Missing template id after embedder {}.\n Hint: Available template: `documentTemplate`.",
format_token(&embedder)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
) )
} else { } else {
indexing.sort_unstable(); indexing.sort_unstable();
search.sort_unstable(); search.sort_unstable();
ResponseError::from_msg( ResponseError::from_msg(
format!("Template ID configured with `embedders.{embedder}` but no template kind provided.\n Hint: Available fragments are {}.", format!("Template ID configured with embedder {} but no template kind provided.\n Hint: Available fragments are {}.",
format_token(&embedder),
indexing.iter().map(|s| format!("`indexingFragments.{s}`")).chain( indexing.iter().map(|s| format!("`indexingFragments.{s}`")).chain(
search.iter().map(|s| format!("`searchFragments.{s}`"))).collect::<Vec<_>>().join(", ")), search.iter().map(|s| format!("`searchFragments.{s}`"))).collect::<Vec<_>>().join(", ")),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
@ -259,14 +274,16 @@ impl From<RenderError<'_>> for ResponseError {
UnknownTemplatePrefix { embedder, found, mut indexing, mut search } => { UnknownTemplatePrefix { embedder, found, mut indexing, mut search } => {
if indexing.is_empty() && search.is_empty() { if indexing.is_empty() && search.is_empty() {
ResponseError::from_msg( ResponseError::from_msg(
format!("Wrong template `{found}` after embedder `{embedder}`.\n Hint: Available template: `documentTemplate`."), format!("Wrong template {} after embedder {}.\n Hint: Available template: `documentTemplate`.", format_token(&found), format_token(&embedder)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
) )
} else { } else {
indexing.sort_unstable(); indexing.sort_unstable();
search.sort_unstable(); search.sort_unstable();
ResponseError::from_msg( ResponseError::from_msg(
format!("Wrong template `{found}` after embedder `{embedder}`.\n Hint: Available fragments are {}.", format!("Wrong template {} after embedder {}.\n Hint: Available fragments are {}.",
format_token(&found),
format_token(&embedder),
indexing.iter().map(|s| format!("`indexingFragments.{s}`")).chain( indexing.iter().map(|s| format!("`indexingFragments.{s}`")).chain(
search.iter().map(|s| format!("`searchFragments.{s}`"))).collect::<Vec<_>>().join(", ")), search.iter().map(|s| format!("`searchFragments.{s}`"))).collect::<Vec<_>>().join(", ")),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
@ -277,9 +294,10 @@ impl From<RenderError<'_>> for ResponseError {
MissingFragment { embedder, kind, mut available } => { MissingFragment { embedder, kind, mut available } => {
available.sort_unstable(); available.sort_unstable();
ResponseError::from_msg( ResponseError::from_msg(
format!("{} fragment name was not provided.\n Hint: Available {} fragments for embedder `{embedder}` are {}.", format!("{} fragment name was not provided.\n Hint: Available {} fragments for embedder {} are {}.",
kind.capitalized(), kind.capitalized(),
kind.as_str(), kind.as_str(),
format_token(&embedder),
available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")), available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
) )
@ -287,15 +305,17 @@ impl From<RenderError<'_>> for ResponseError {
FragmentDoesNotExist { embedder, fragment, kind, mut available } => { FragmentDoesNotExist { embedder, fragment, kind, mut available } => {
available.sort_unstable(); available.sort_unstable();
ResponseError::from_msg( ResponseError::from_msg(
format!("{} fragment `{fragment}` does not exist for embedder `{embedder}`.\n Hint: Available {} fragments are {}.", format!("{} fragment {} does not exist for embedder {}.\n Hint: Available {} fragments are {}.",
kind.capitalized(), kind.capitalized(),
format_token(&fragment),
format_token(&embedder),
kind.as_str(), kind.as_str(),
available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")), available.iter().map(|s| format!("`{s}`")).collect::<Vec<_>>().join(", ")),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
) )
}, },
LeftOverToken(token) => ResponseError::from_msg( LeftOverToken(token) => ResponseError::from_msg(
format!("Leftover token `{token}` after parsing template ID"), format!("Leftover token {} after parsing template ID", format_token(&token)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
MissingChatCompletionTemplate => ResponseError::from_msg( MissingChatCompletionTemplate => ResponseError::from_msg(
@ -303,7 +323,7 @@ impl From<RenderError<'_>> for ResponseError {
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
UnknownChatCompletionTemplate(id) => ResponseError::from_msg( UnknownChatCompletionTemplate(id) => ResponseError::from_msg(
format!("Unknown chat completion template ID `{id}`. The only available template is `documentTemplate`."), format!("Unknown chat completion template ID {}. The only available template is `documentTemplate`.", format_token(&id)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
DocumentNotFound(doc_id) => ResponseError::from_msg( DocumentNotFound(doc_id) => ResponseError::from_msg(
@ -331,11 +351,11 @@ impl From<RenderError<'_>> for ResponseError {
Code::InvalidRenderInput, Code::InvalidRenderInput,
), ),
ExpectedDotAfterValue(span) => ResponseError::from_msg( ExpectedDotAfterValue(span) => ResponseError::from_msg(
format!("Expected a dot after value, but found `{span}`."), format!("Expected a dot after value, but found {}.", format_span(&span)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
ExpectedValue(span) => ResponseError::from_msg( ExpectedValue(span) => ResponseError::from_msg(
format!("Expected a value, but found `{span}`."), format!("Expected a value, but found {}.", format_span(&span)),
Code::InvalidRenderTemplateId, Code::InvalidRenderTemplateId,
), ),
} }
@ -570,7 +590,7 @@ pub struct RenderQueryTemplate {
#[deserr(default, error = DeserrJsonError<InvalidRenderTemplateId>)] #[deserr(default, error = DeserrJsonError<InvalidRenderTemplateId>)]
pub id: Option<String>, pub id: Option<String>,
#[deserr(default, error = DeserrJsonError<InvalidRenderTemplateInline>)] #[deserr(default, error = DeserrJsonError<InvalidRenderTemplateInline>)]
pub inline: Option<serde_json::Value>, pub inline: Option<Value>,
} }
#[derive(Debug, Clone, Default, PartialEq, Deserr, ToSchema)] #[derive(Debug, Clone, Default, PartialEq, Deserr, ToSchema)]
@ -579,11 +599,11 @@ pub struct RenderQueryInput {
#[deserr(default, error = DeserrJsonError<InvalidRenderInputDocumentId>)] #[deserr(default, error = DeserrJsonError<InvalidRenderInputDocumentId>)]
pub document_id: Option<String>, pub document_id: Option<String>,
#[deserr(default, error = DeserrJsonError<InvalidRenderInputInline>)] #[deserr(default, error = DeserrJsonError<InvalidRenderInputInline>)]
pub inline: Option<BTreeMap<String, serde_json::Value>>, pub inline: Option<BTreeMap<String, Value>>,
} }
#[derive(Debug, Clone, Serialize, PartialEq, ToSchema)] #[derive(Debug, Clone, Serialize, PartialEq, ToSchema)]
pub struct RenderResult { pub struct RenderResult {
template: serde_json::Value, template: Value,
rendered: serde_json::Value, rendered: Value,
} }

View File

@ -26,7 +26,7 @@ async fn wrong_id_prefix() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Template ID must start with `embedders` or `chatCompletions`, but found `{wrong}`.", "message": "Template ID must start with `embedders` or `chatCompletions`, but found `wrong` (cols 1:6).",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -59,7 +59,7 @@ async fn wrong_embedder() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Embedder `{wrong}` does not exist.\n Hint: Available embedders are `rest`.", "message": "Embedder `wrong` (cols 11:16) does not exist.\n Hint: Available embedders are `rest`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -75,7 +75,7 @@ async fn missing_template_kind() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Template ID configured with `embedders.{rest}` but no template kind provided.\n Hint: Available fragments are `indexingFragments.basic`, `indexingFragments.withBreed`, `searchFragments.justBreed`, `searchFragments.justName`, `searchFragments.query`.", "message": "Template ID configured with embedder `rest` (cols 11:15) but no template kind provided.\n Hint: Available fragments are `indexingFragments.basic`, `indexingFragments.withBreed`, `searchFragments.justBreed`, `searchFragments.justName`, `searchFragments.query`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -92,7 +92,7 @@ async fn wrong_template_kind() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Wrong template `{wrong}` after embedder `{rest}`.\n Hint: Available fragments are `indexingFragments.basic`, `indexingFragments.withBreed`, `searchFragments.justBreed`, `searchFragments.justName`, `searchFragments.query`.", "message": "Wrong template `wrong` (cols 16:21) after embedder `rest` (cols 11:15).\n Hint: Available fragments are `indexingFragments.basic`, `indexingFragments.withBreed`, `searchFragments.justBreed`, `searchFragments.justName`, `searchFragments.query`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -109,7 +109,7 @@ async fn document_template_on_fragmented_index() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Requested document template for embedder `{rest}` but it uses fragments.\n Hint: Use `indexingFragments` or `searchFragments` instead.", "message": "Requested document template for embedder `rest` (cols 11:15) but it uses fragments.\n Hint: Use `indexingFragments` or `searchFragments` instead.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -126,7 +126,7 @@ async fn missing_fragment_name() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Indexing fragment name was not provided.\n Hint: Available indexing fragments for embedder `{rest}` are `basic`, `withBreed`.", "message": "Indexing fragment name was not provided.\n Hint: Available indexing fragments for embedder `rest` (cols 11:15) are `basic`, `withBreed`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -138,7 +138,7 @@ async fn missing_fragment_name() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Search fragment name was not provided.\n Hint: Available search fragments for embedder `{rest}` are `justBreed`, `justName`, `query`.", "message": "Search fragment name was not provided.\n Hint: Available search fragments for embedder `rest` (cols 11:15) are `justBreed`, `justName`, `query`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -156,7 +156,7 @@ async fn wrong_fragment_name() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Indexing fragment `{wrong}` does not exist for embedder `{rest}`.\n Hint: Available indexing fragments are `basic`, `withBreed`.", "message": "Indexing fragment `wrong` (cols 34:39) does not exist for embedder `rest` (cols 11:15).\n Hint: Available indexing fragments are `basic`, `withBreed`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -168,7 +168,7 @@ async fn wrong_fragment_name() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Search fragment `{wrong}` does not exist for embedder `{rest}`.\n Hint: Available search fragments are `justBreed`, `justName`, `query`.", "message": "Search fragment `wrong` (cols 32:37) does not exist for embedder `rest` (cols 11:15).\n Hint: Available search fragments are `justBreed`, `justName`, `query`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -188,7 +188,7 @@ async fn leftover_tokens() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Leftover token `{leftover}` after parsing template ID", "message": "Leftover token `leftover` (cols 44:52) after parsing template ID",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -201,7 +201,7 @@ async fn leftover_tokens() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Leftover token `{leftover}` after parsing template ID", "message": "Leftover token `leftover` (cols 42:50) after parsing template ID",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -214,7 +214,7 @@ async fn leftover_tokens() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Leftover token `{leftover}` after parsing template ID", "message": "Leftover token `leftover` (cols 34:42) after parsing template ID",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -274,7 +274,7 @@ async fn wrong_chat_completions_template() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Unknown chat completion template ID `{wrong}`. The only available template is `documentTemplate`.", "message": "Unknown chat completion template ID `wrong` (cols 17:22). The only available template is `documentTemplate`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"
@ -638,7 +638,7 @@ async fn embedder_document_template() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
"message": "Wrong template `{wrong}` after embedder `{rest}`.\n Hint: Available template: `documentTemplate`.", "message": "Wrong template `wrong` (cols 16:21) after embedder `rest` (cols 11:15).\n Hint: Available template: `documentTemplate`.",
"code": "invalid_render_template_id", "code": "invalid_render_template_id",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_render_template_id" "link": "https://docs.meilisearch.com/errors#invalid_render_template_id"