1use std::collections::BTreeSet;
2use std::collections::HashMap;
3use std::convert::Infallible;
4use std::fmt::Write;
5use std::{io, str};
6
7use bstr::BString;
8use heed::{Error as HeedError, MdbError};
9use rayon::ThreadPoolBuildError;
10use rhai::EvalAltResult;
11use serde_json::Value;
12use thiserror::Error;
13
14use crate::constants::RESERVED_GEO_FIELD_NAME;
15use crate::documents::{self, DocumentsBatchCursorError};
16use crate::thread_pool_no_abort::PanicCatched;
17use crate::vector::settings::EmbeddingSettings;
18use crate::{CriterionError, DocumentId, FieldId, Object, SortError};
19
20pub fn is_reserved_keyword(keyword: &str) -> bool {
21 [RESERVED_GEO_FIELD_NAME, "_geoDistance", "_geoPoint", "_geoRadius", "_geoBoundingBox"]
22 .contains(&keyword)
23}
24
25#[derive(Error, Debug)]
26pub enum Error {
27 #[error("internal: {0}.")]
28 InternalError(#[from] InternalError),
29 #[error(transparent)]
30 IoError(#[from] io::Error),
31 #[error(transparent)]
32 UserError(#[from] UserError),
33}
34
35#[derive(Error, Debug)]
36pub enum InternalError {
37 #[error("missing {} in the {db_name} database", key.unwrap_or("key"))]
38 DatabaseMissingEntry { db_name: &'static str, key: Option<&'static str> },
39 #[error("missing {key} in the fieldids weights mapping")]
40 FieldidsWeightsMapMissingEntry { key: FieldId },
41 #[error(transparent)]
42 FieldIdMapMissingEntry(#[from] FieldIdMapMissingEntry),
43 #[error("missing {key} in the field id mapping")]
44 FieldIdMappingMissingEntry { key: FieldId },
45 #[error(transparent)]
46 Fst(#[from] fst::Error),
47 #[error(transparent)]
48 DocumentsError(#[from] documents::Error),
49 #[error("invalid compression type have been specified to grenad")]
50 GrenadInvalidCompressionType,
51 #[error("invalid grenad file with an invalid version format")]
52 GrenadInvalidFormatVersion,
53 #[error("invalid merge while processing {process}")]
54 IndexingMergingKeys { process: &'static str },
55 #[error(transparent)]
56 RayonThreadPool(#[from] ThreadPoolBuildError),
57 #[error(transparent)]
58 PanicInThreadPool(#[from] PanicCatched),
59 #[error(transparent)]
60 SerdeJson(#[from] serde_json::Error),
61 #[error(transparent)]
62 BincodeError(#[from] bincode::Error),
63 #[error(transparent)]
64 Serialization(#[from] SerializationError),
65 #[error(transparent)]
66 Store(#[from] MdbError),
67 #[error("Cannot delete {key:?} from database {database_name}: {error}")]
68 StoreDeletion { database_name: &'static str, key: BString, error: heed::Error },
69 #[error("Cannot insert {key:?} and value with length {value_length} into database {database_name}: {error}")]
70 StorePut { database_name: &'static str, key: BString, value_length: usize, error: heed::Error },
71 #[error(transparent)]
72 Utf8(#[from] str::Utf8Error),
73 #[error("An indexation process was explicitly aborted")]
74 AbortedIndexation,
75 #[error("The matching words list contains at least one invalid member")]
76 InvalidMatchingWords,
77 #[error("Cannot upgrade to the following version: v{0}.{1}.{2}.")]
78 CannotUpgradeToVersion(u32, u32, u32),
79 #[error(transparent)]
80 ArroyError(#[from] arroy::Error),
81 #[error(transparent)]
82 VectorEmbeddingError(#[from] crate::vector::Error),
83}
84
85#[derive(Error, Debug)]
86pub enum SerializationError {
87 #[error("{}", match .db_name {
88 Some(name) => format!("decoding from the {name} database failed"),
89 None => "decoding failed".to_string(),
90 })]
91 Decoding { db_name: Option<&'static str> },
92 #[error("{}", match .db_name {
93 Some(name) => format!("encoding into the {name} database failed"),
94 None => "encoding failed".to_string(),
95 })]
96 Encoding { db_name: Option<&'static str> },
97 #[error("number is not a valid finite number")]
98 InvalidNumberSerialization,
99}
100
101#[derive(Error, Debug)]
102pub enum FieldIdMapMissingEntry {
103 #[error("unknown field id {field_id} coming from the {process} process")]
104 FieldId { field_id: FieldId, process: &'static str },
105 #[error("unknown field name {field_name} coming from the {process} process")]
106 FieldName { field_name: String, process: &'static str },
107}
108
109#[derive(Error, Debug)]
110pub enum UserError {
111 #[error("A document cannot contain more than 65,535 fields.")]
112 AttributeLimitReached,
113 #[error(transparent)]
114 CriterionError(#[from] CriterionError),
115 #[error("Maximum number of documents reached.")]
116 DocumentLimitReached,
117 #[error(
118 "Document identifier `{}` is invalid. \
119A document identifier can be of type integer or string, \
120only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_), \
121and can not be more than 511 bytes.", .document_id.to_string()
122 )]
123 InvalidDocumentId { document_id: Value },
124 #[error("Invalid facet distribution: {}",
125 if .invalid_facets_name.len() == 1 {
126 let field = .invalid_facets_name.iter().next().unwrap();
127 match .matching_rule_indices.get(field) {
128 Some(rule_index) => format!("Attribute `{}` matched rule #{} in filterableAttributes, but this rule does not enable filtering.\nHint: enable filtering in rule #{} by modifying the features.filter object\nHint: prepend another rule matching `{}` with appropriate filter features before rule #{}",
129 field, rule_index, rule_index, field, rule_index),
130 None => match .valid_patterns.is_empty() {
131 true => format!("Attribute `{}` is not filterable. This index does not have configured filterable attributes.", field),
132 false => format!("Attribute `{}` is not filterable. Available filterable attributes patterns are: `{}`.",
133 field,
134 .valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")),
135 }
136 }
137 } else {
138 format!("Attributes `{}` are not filterable. {}",
139 .invalid_facets_name.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
140 match .valid_patterns.is_empty() {
141 true => "This index does not have configured filterable attributes.".to_string(),
142 false => format!("Available filterable attributes patterns are: `{}`.",
143 .valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")),
144 }
145 )
146 }
147 )]
148 InvalidFacetsDistribution {
149 invalid_facets_name: BTreeSet<String>,
150 valid_patterns: BTreeSet<String>,
151 matching_rule_indices: HashMap<String, usize>,
152 },
153 #[error(transparent)]
154 InvalidGeoField(#[from] Box<GeoError>),
155 #[error("Invalid vector dimensions: expected: `{}`, found: `{}`.", .expected, .found)]
156 InvalidVectorDimensions { expected: usize, found: usize },
157 #[error("Invalid vector dimensions in document with id `{document_id}` in `._vectors.{embedder_name}`.\n - note: embedding #{embedding_index} has dimensions {found}\n - note: embedder `{embedder_name}` requires {expected}")]
158 InvalidIndexingVectorDimensions {
159 embedder_name: String,
160 document_id: String,
161 embedding_index: usize,
162 expected: usize,
163 found: usize,
164 },
165 #[error("The `_vectors` field in the document with id: `{document_id}` is not an object. Was expecting an object with a key for each embedder with manually provided vectors, but instead got `{value}`")]
166 InvalidVectorsMapType { document_id: String, value: Value },
167 #[error("Bad embedder configuration in the document with id: `{document_id}`. {error}")]
168 InvalidVectorsEmbedderConf { document_id: String, error: String },
169 #[error("{0}")]
170 InvalidFilter(String),
171 #[error("Invalid type for filter subexpression: expected: {}, found: {}.", .0.join(", "), .1)]
172 InvalidFilterExpression(&'static [&'static str], Value),
173 #[error("Filter operator `{operator}` is not allowed for the attribute `{field}`.\n - Note: allowed operators: {}.\n - Note: field `{field}` matched rule #{rule_index} in `filterableAttributes`\n - Hint: enable {} in rule #{rule_index} by modifying the features.filter object\n - Hint: prepend another rule matching `{field}` with appropriate filter features before rule #{rule_index}",
174 allowed_operators.join(", "),
175 if operator == "=" || operator == "!=" || operator == "IN" {"equality"}
176 else if operator == "<" || operator == ">" || operator == "<=" || operator == ">=" || operator == "TO" {"comparison"}
177 else {"the appropriate filter operators"}
178 )]
179 FilterOperatorNotAllowed {
180 field: String,
181 allowed_operators: Vec<String>,
182 operator: String,
183 rule_index: usize,
184 },
185 #[error("Attribute `{}` is not sortable. {}",
186 .field,
187 match .valid_fields.is_empty() {
188 true => "This index does not have configured sortable attributes.".to_string(),
189 false => format!("Available sortable attributes are: `{}{}`.",
190 valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
191 .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
192 ),
193 }
194 )]
195 InvalidSortableAttribute { field: String, valid_fields: BTreeSet<String>, hidden_fields: bool },
196 #[error("Attribute `{}` is not filterable and thus, cannot be used as distinct attribute. {}",
197 .field,
198 match (.valid_patterns.is_empty(), .matching_rule_index) {
199 (true, None) => "This index does not have configured filterable attributes.".to_string(),
201
202 (false, None) => format!("Available filterable attributes patterns are: `{}{}`.",
204 valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
205 .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
206 ),
207
208 (_, Some(rule_index)) => format!("Note: this attribute matches rule #{} in filterableAttributes, but this rule does not enable filtering.\nHint: enable filtering in rule #{} by adding appropriate filter features.\nHint: prepend another rule matching {} with filter features before rule #{}",
210 rule_index, rule_index, .field, rule_index
211 ),
212 }
213 )]
214 InvalidDistinctAttribute {
215 field: String,
216 valid_patterns: BTreeSet<String>,
217 hidden_fields: bool,
218 matching_rule_index: Option<usize>,
219 },
220 #[error("Attribute `{}` is not facet-searchable. {}",
221 .field,
222 match (.valid_patterns.is_empty(), .matching_rule_index) {
223 (true, None) => "This index does not have configured facet-searchable attributes. To make it facet-searchable add it to the `filterableAttributes` index settings.".to_string(),
225
226 (false, None) => format!("Available facet-searchable attributes patterns are: `{}{}`. To make it facet-searchable add it to the `filterableAttributes` index settings.",
228 valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
229 .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
230 ),
231
232 (_, Some(rule_index)) => format!("Note: this attribute matches rule #{} in filterableAttributes, but this rule does not enable facetSearch.\nHint: enable facetSearch in rule #{} by adding `\"facetSearch\": true` to the rule.\nHint: prepend another rule matching {} with facetSearch: true before rule #{}",
234 rule_index, rule_index, .field, rule_index
235 ),
236 }
237 )]
238 InvalidFacetSearchFacetName {
239 field: String,
240 valid_patterns: BTreeSet<String>,
241 hidden_fields: bool,
242 matching_rule_index: Option<usize>,
243 },
244 #[error("Attribute `{}` is not searchable. Available searchable attributes are: `{}{}`.",
245 .field,
246 .valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
247 .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
248 )]
249 InvalidSearchableAttribute {
250 field: String,
251 valid_fields: BTreeSet<String>,
252 hidden_fields: bool,
253 },
254 #[error("An LMDB environment is already opened")]
255 EnvAlreadyOpened,
256 #[error("You must specify where `sort` is listed in the rankingRules setting to use the sort parameter at search time.")]
257 SortRankingRuleMissing,
258 #[error("The database file is in an invalid state.")]
259 InvalidStoreFile,
260 #[error("Maximum database size has been reached.")]
261 MaxDatabaseSizeReached,
262 #[error("Document doesn't have a `{}` attribute: `{}`.", .primary_key, serde_json::to_string(.document).unwrap())]
263 MissingDocumentId { primary_key: String, document: Object },
264 #[error("Document have too many matching `{}` attribute: `{}`.", .primary_key, serde_json::to_string(.document).unwrap())]
265 TooManyDocumentIds { primary_key: String, document: Object },
266 #[error("The primary key inference failed as the engine did not find any field ending with `id` in its name. Please specify the primary key manually using the `primaryKey` query parameter.")]
267 NoPrimaryKeyCandidateFound,
268 #[error("The primary key inference failed as the engine found {} fields ending with `id` in their names: '{}' and '{}'. Please specify the primary key manually using the `primaryKey` query parameter.", .candidates.len(), .candidates.first().unwrap(), .candidates.get(1).unwrap())]
269 MultiplePrimaryKeyCandidatesFound { candidates: Vec<String> },
270 #[error("There is no more space left on the device. Consider increasing the size of the disk/partition.")]
271 NoSpaceLeftOnDevice,
272 #[error("Index already has a primary key: `{0}`.")]
273 PrimaryKeyCannotBeChanged(String),
274 #[error(transparent)]
275 SerdeJson(serde_json::Error),
276 #[error(transparent)]
277 SortError(#[from] SortError),
278 #[error("An unknown internal document id have been used: `{document_id}`.")]
279 UnknownInternalDocumentId { document_id: DocumentId },
280 #[error("`minWordSizeForTypos` setting is invalid. `oneTypo` and `twoTypos` fields should be between `0` and `255`, and `twoTypos` should be greater or equals to `oneTypo` but found `oneTypo: {0}` and twoTypos: {1}`.")]
281 InvalidMinTypoWordLenSetting(u8, u8),
282 #[error(transparent)]
283 VectorEmbeddingError(#[from] crate::vector::Error),
284 #[error(transparent)]
285 MissingDocumentField(#[from] crate::prompt::error::RenderPromptError),
286 #[error(transparent)]
287 InvalidPrompt(#[from] crate::prompt::error::NewPromptError),
288 #[error("`.embedders.{0}.documentTemplate`: Invalid template: {1}.")]
289 InvalidPromptForEmbeddings(String, crate::prompt::error::NewPromptError),
290 #[error("Too many embedders in the configuration. Found {0}, but limited to 256.")]
291 TooManyEmbedders(usize),
292 #[error("Cannot find embedder with name `{0}`.")]
293 InvalidSearchEmbedder(String),
294 #[error("Cannot find embedder with name `{0}`.")]
295 InvalidSimilarEmbedder(String),
296 #[error("Too many vectors for document with id {0}: found {1}, but limited to 256.")]
297 TooManyVectors(String, usize),
298 #[error("`.embedders.{embedder_name}`: Field `{field}` unavailable for source `{source_}`{for_context}.{available_sources}{available_fields}{available_contexts}",
299 field=field.name(),
300 for_context={
301 context.in_context()
302 },
303 available_sources={
304 let allowed_sources_for_field = EmbeddingSettings::allowed_sources_for_field(*field, *context);
305 if allowed_sources_for_field.is_empty() {
306 String::new()
307 } else {
308 format!("\n - note: `{}` is available for sources: {}",
309 field.name(),
310 allowed_sources_for_field
311 .iter()
312 .map(|accepted| format!("`{}`", accepted))
313 .collect::<Vec<String>>()
314 .join(", "),
315 )
316 }
317 },
318 available_fields={
319 let allowed_fields_for_source = EmbeddingSettings::allowed_fields_for_source(*source_, *context);
320 format!("\n - note: available fields for source `{source_}`{}: {}",context.in_context(), allowed_fields_for_source
321 .iter()
322 .map(|accepted| format!("`{}`", accepted))
323 .collect::<Vec<String>>()
324 .join(", "),)
325 },
326 available_contexts={
327 let available_not_nested = !matches!(EmbeddingSettings::field_status(*source_, *field, crate::vector::settings::NestingContext::NotNested), crate::vector::settings::FieldStatus::Disallowed);
328 if available_not_nested {
329 format!("\n - note: `{}` is available when source `{source_}` is not{}", field.name(), context.in_context())
330 } else {
331 String::new()
332 }
333 }
334 )]
335 InvalidFieldForSource {
336 embedder_name: String,
337 source_: crate::vector::settings::EmbedderSource,
338 context: crate::vector::settings::NestingContext,
339 field: crate::vector::settings::MetaEmbeddingSetting,
340 },
341 #[error("`.embedders.{embedder_name}.model`: Invalid model `{model}` for OpenAI. Supported models: {:?}", crate::vector::openai::EmbeddingModel::supported_models())]
342 InvalidOpenAiModel { embedder_name: String, model: String },
343 #[error("`.embedders.{embedder_name}`: Missing field `{field}` (note: this field is mandatory for source `{source_}`)")]
344 MissingFieldForSource {
345 field: &'static str,
346 source_: crate::vector::settings::EmbedderSource,
347 embedder_name: String,
348 },
349 #[error("`.embedders.{embedder_name}.dimensions`: Model `{model}` does not support overriding its native dimensions of {expected_dimensions}. Found {dimensions}")]
350 InvalidOpenAiModelDimensions {
351 embedder_name: String,
352 model: &'static str,
353 dimensions: usize,
354 expected_dimensions: usize,
355 },
356 #[error("`.embedders.{embedder_name}.dimensions`: Model `{model}` does not support overriding its dimensions to a value higher than {max_dimensions}. Found {dimensions}")]
357 InvalidOpenAiModelDimensionsMax {
358 embedder_name: String,
359 model: &'static str,
360 dimensions: usize,
361 max_dimensions: usize,
362 },
363 #[error("`.embedders.{embedder_name}.source`: Source `{source_}` is not available in a nested embedder")]
364 InvalidSourceForNested {
365 embedder_name: String,
366 source_: crate::vector::settings::EmbedderSource,
367 },
368 #[error("`.embedders.{embedder_name}`: Missing field `source`.\n - note: this field is mandatory for nested embedders")]
369 MissingSourceForNested { embedder_name: String },
370 #[error("`.embedders.{embedder_name}`: {message}")]
371 InvalidSettingsEmbedder { embedder_name: String, message: String },
372 #[error("`.embedders.{embedder_name}.dimensions`: `dimensions` cannot be zero")]
373 InvalidSettingsDimensions { embedder_name: String },
374 #[error(
375 "`.embedders.{embedder_name}.binaryQuantized`: Cannot disable the binary quantization.\n - Note: Binary quantization is a lossy operation that cannot be reverted.\n - Hint: Add a new embedder that is non-quantized and regenerate the vectors."
376 )]
377 InvalidDisableBinaryQuantization { embedder_name: String },
378 #[error("`.embedders.{embedder_name}.documentTemplateMaxBytes`: `documentTemplateMaxBytes` cannot be zero")]
379 InvalidSettingsDocumentTemplateMaxBytes { embedder_name: String },
380 #[error("`.embedders.{embedder_name}.url`: could not parse `{url}`: {inner_error}")]
381 InvalidUrl { embedder_name: String, inner_error: url::ParseError, url: String },
382 #[error("Document editions cannot modify a document's primary key")]
383 DocumentEditionCannotModifyPrimaryKey,
384 #[error("Document editions must keep documents as objects")]
385 DocumentEditionDocumentMustBeObject,
386 #[error("Document edition runtime error encountered while running the function: {0}")]
387 DocumentEditionRuntimeError(Box<EvalAltResult>),
388 #[error("Document edition runtime error encountered while compiling the function: {0}")]
389 DocumentEditionCompilationError(rhai::ParseError),
390 #[error("{0}")]
391 DocumentEmbeddingError(String),
392}
393
394impl From<crate::vector::Error> for Error {
395 fn from(value: crate::vector::Error) -> Self {
396 match value.fault() {
397 FaultSource::User => Error::UserError(value.into()),
398 FaultSource::Runtime => Error::UserError(value.into()),
399 FaultSource::Bug => Error::InternalError(value.into()),
400 FaultSource::Undecided => Error::UserError(value.into()),
401 }
402 }
403}
404
405impl From<arroy::Error> for Error {
406 fn from(value: arroy::Error) -> Self {
407 match value {
408 arroy::Error::Heed(heed) => heed.into(),
409 arroy::Error::Io(io) => io.into(),
410 arroy::Error::InvalidVecDimension { expected, received } => {
411 Error::UserError(UserError::InvalidVectorDimensions { expected, found: received })
412 }
413 arroy::Error::BuildCancelled => Error::InternalError(InternalError::AbortedIndexation),
414 arroy::Error::DatabaseFull
415 | arroy::Error::InvalidItemAppend
416 | arroy::Error::UnmatchingDistance { .. }
417 | arroy::Error::NeedBuild(_)
418 | arroy::Error::MissingKey { .. }
419 | arroy::Error::MissingMetadata(_)
420 | arroy::Error::CannotDecodeKeyMode { .. } => {
421 Error::InternalError(InternalError::ArroyError(value))
422 }
423 }
424 }
425}
426
427#[derive(Error, Debug)]
428pub enum GeoError {
429 #[error("The `_geo` field in the document with the id: `{document_id}` is not an object. Was expecting an object with the `_geo.lat` and `_geo.lng` fields but instead got `{value}`.")]
430 NotAnObject { document_id: Value, value: Value },
431 #[error("The `_geo` field in the document with the id: `{document_id}` contains the following unexpected fields: `{value}`.")]
432 UnexpectedExtraFields { document_id: Value, value: Value },
433 #[error("Could not find latitude nor longitude in the document with the id: `{document_id}`. Was expecting `_geo.lat` and `_geo.lng` fields.")]
434 MissingLatitudeAndLongitude { document_id: Value },
435 #[error("Could not find latitude in the document with the id: `{document_id}`. Was expecting a `_geo.lat` field.")]
436 MissingLatitude { document_id: Value },
437 #[error("Could not find longitude in the document with the id: `{document_id}`. Was expecting a `_geo.lng` field.")]
438 MissingLongitude { document_id: Value },
439 #[error("Could not parse latitude nor longitude in the document with the id: `{document_id}`. Was expecting finite numbers but instead got `{lat}` and `{lng}`.")]
440 BadLatitudeAndLongitude { document_id: Value, lat: Value, lng: Value },
441 #[error("Could not parse latitude in the document with the id: `{document_id}`. Was expecting a finite number but instead got `{value}`.")]
442 BadLatitude { document_id: Value, value: Value },
443 #[error("Could not parse longitude in the document with the id: `{document_id}`. Was expecting a finite number but instead got `{value}`.")]
444 BadLongitude { document_id: Value, value: Value },
445}
446
447#[allow(dead_code)]
448fn format_invalid_filter_distribution(
449 invalid_facets_name: &BTreeSet<String>,
450 valid_patterns: &BTreeSet<String>,
451) -> String {
452 let mut result = String::new();
453
454 if invalid_facets_name.is_empty() {
455 if valid_patterns.is_empty() {
456 return "this index does not have configured filterable attributes.".into();
457 }
458 } else {
459 match invalid_facets_name.len() {
460 1 => write!(
461 result,
462 "Attribute `{}` is not filterable.",
463 invalid_facets_name.first().unwrap()
464 )
465 .unwrap(),
466 _ => write!(
467 result,
468 "Attributes `{}` are not filterable.",
469 invalid_facets_name.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
470 )
471 .unwrap(),
472 };
473 }
474
475 if valid_patterns.is_empty() {
476 if !invalid_facets_name.is_empty() {
477 write!(result, " This index does not have configured filterable attributes.").unwrap();
478 }
479 } else {
480 match valid_patterns.len() {
481 1 => write!(
482 result,
483 " Available filterable attributes patterns are: `{}`.",
484 valid_patterns.first().unwrap()
485 )
486 .unwrap(),
487 _ => write!(
488 result,
489 " Available filterable attributes patterns are: `{}`.",
490 valid_patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
491 )
492 .unwrap(),
493 }
494 }
495
496 result
497}
498
499macro_rules! error_from_sub_error {
510 () => {};
511 ($sub:ty => $intermediate:ty) => {
512 impl From<$sub> for Error {
513 fn from(error: $sub) -> Error {
514 Error::from(<$intermediate>::from(error))
515 }
516 }
517 };
518 ($($sub:ty => $intermediate:ty $(,)?),+) => {
519 $(error_from_sub_error!($sub => $intermediate);)+
520 };
521}
522
523error_from_sub_error! {
524 FieldIdMapMissingEntry => InternalError,
525 fst::Error => InternalError,
526 documents::Error => InternalError,
527 str::Utf8Error => InternalError,
528 ThreadPoolBuildError => InternalError,
529 SerializationError => InternalError,
530 Box<GeoError> => UserError,
531 CriterionError => UserError,
532}
533
534impl<E> From<grenad::Error<E>> for Error
535where
536 Error: From<E>,
537{
538 fn from(error: grenad::Error<E>) -> Error {
539 match error {
540 grenad::Error::Io(error) => Error::IoError(error),
541 grenad::Error::Merge(error) => Error::from(error),
542 grenad::Error::InvalidCompressionType => {
543 Error::InternalError(InternalError::GrenadInvalidCompressionType)
544 }
545 grenad::Error::InvalidFormatVersion => {
546 Error::InternalError(InternalError::GrenadInvalidFormatVersion)
547 }
548 }
549 }
550}
551
552impl From<DocumentsBatchCursorError> for Error {
553 fn from(error: DocumentsBatchCursorError) -> Error {
554 match error {
555 DocumentsBatchCursorError::Grenad(e) => Error::from(e),
556 DocumentsBatchCursorError::SerdeJson(e) => Error::from(InternalError::from(e)),
557 }
558 }
559}
560
561impl From<Infallible> for Error {
562 fn from(_error: Infallible) -> Error {
563 unreachable!()
564 }
565}
566
567impl From<HeedError> for Error {
568 fn from(error: HeedError) -> Error {
569 use self::Error::*;
570 use self::InternalError::*;
571 use self::SerializationError::*;
572 use self::UserError::*;
573
574 match error {
575 HeedError::Io(error) => Error::from(error),
576 HeedError::Mdb(MdbError::MapFull) => UserError(MaxDatabaseSizeReached),
577 HeedError::Mdb(MdbError::Invalid) => UserError(InvalidStoreFile),
578 HeedError::Mdb(error) => InternalError(Store(error)),
579 HeedError::Encoding(_) => InternalError(Serialization(Encoding { db_name: None })),
581 HeedError::Decoding(_) => InternalError(Serialization(Decoding { db_name: None })),
582 HeedError::EnvAlreadyOpened { .. } => UserError(EnvAlreadyOpened),
583 }
584 }
585}
586
587#[derive(Debug, Clone, Copy)]
588pub enum FaultSource {
589 User,
590 Runtime,
591 Bug,
592 Undecided,
593}
594
595impl std::fmt::Display for FaultSource {
596 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597 let s = match self {
598 FaultSource::User => "user error",
599 FaultSource::Runtime => "runtime error",
600 FaultSource::Bug => "coding error",
601 FaultSource::Undecided => "error",
602 };
603 f.write_str(s)
604 }
605}
606
607#[test]
608fn conditionally_lookup_for_error_message() {
609 let prefix = "Attribute `name` is not sortable.";
610 let messages = vec![
611 (BTreeSet::new(), "This index does not have configured sortable attributes."),
612 (BTreeSet::from(["age".to_string()]), "Available sortable attributes are: `age`."),
613 ];
614
615 for (list, suffix) in messages {
616 let err = UserError::InvalidSortableAttribute {
617 field: "name".to_string(),
618 valid_fields: list,
619 hidden_fields: false,
620 };
621
622 assert_eq!(err.to_string(), format!("{} {}", prefix, suffix));
623 }
624}