2021-10-28 11:18:32 +02:00
use std ::collections ::BTreeSet ;
2021-06-14 16:58:38 +02:00
use std ::convert ::Infallible ;
2022-05-02 20:10:25 +02:00
use std ::{ io , str } ;
2021-06-09 17:05:46 +02:00
2021-06-16 18:33:33 +02:00
use heed ::{ Error as HeedError , MdbError } ;
2021-06-14 16:58:38 +02:00
use rayon ::ThreadPoolBuildError ;
2021-06-10 17:31:08 +02:00
use serde_json ::{ Map , Value } ;
2022-05-02 20:10:25 +02:00
use thiserror ::Error ;
2021-06-10 17:31:08 +02:00
2021-09-27 19:07:22 +02:00
use crate ::{ CriterionError , DocumentId , FieldId , SortError } ;
2021-06-10 15:55:22 +02:00
2021-06-10 17:31:08 +02:00
pub type Object = Map < String , Value > ;
2021-06-09 17:05:46 +02:00
2021-09-02 15:57:40 +02:00
pub fn is_reserved_keyword ( keyword : & str ) -> bool {
2021-09-20 17:21:02 +02:00
[ " _geo " , " _geoDistance " , " _geoPoint " , " _geoRadius " ] . contains ( & keyword )
2021-09-02 15:57:40 +02:00
}
2022-05-02 20:10:25 +02:00
#[ derive(Error, Debug) ]
2021-06-09 17:05:46 +02:00
pub enum Error {
2022-05-02 20:10:25 +02:00
#[ error( " internal: {0}. " ) ]
InternalError ( #[ from ] InternalError ) ,
#[ error(transparent) ]
IoError ( #[ from ] io ::Error ) ,
#[ error(transparent) ]
UserError ( #[ from ] UserError ) ,
2021-06-09 17:05:46 +02:00
}
2022-05-02 20:10:25 +02:00
#[ derive(Error, Debug) ]
2021-06-09 17:05:46 +02:00
pub enum InternalError {
2022-05-02 20:10:25 +02:00
#[ error( " {} " , HeedError::DatabaseClosing) ]
2021-06-14 16:58:38 +02:00
DatabaseClosing ,
2022-05-02 20:10:25 +02:00
#[ error( " Missing {} in the {db_name} database. " , key.unwrap_or( " key " )) ]
2021-06-10 17:31:08 +02:00
DatabaseMissingEntry { db_name : & 'static str , key : Option < & 'static str > } ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
FieldIdMapMissingEntry ( #[ from ] FieldIdMapMissingEntry ) ,
#[ error( " Missing {key} in the field id mapping. " ) ]
2022-03-23 17:28:41 +01:00
FieldIdMappingMissingEntry { key : FieldId } ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
Fst ( #[ from ] fst ::Error ) ,
#[ error( " Invalid compression type have been specified to grenad. " ) ]
2021-06-14 16:58:38 +02:00
GrenadInvalidCompressionType ,
2022-05-02 20:10:25 +02:00
#[ error( " Invalid grenad file with an invalid version format. " ) ]
2022-02-16 15:28:48 +01:00
GrenadInvalidFormatVersion ,
2022-05-02 20:10:25 +02:00
#[ error( " Invalid merge while processing {process}. " ) ]
2021-06-10 17:31:08 +02:00
IndexingMergingKeys { process : & 'static str } ,
2022-05-02 20:10:25 +02:00
#[ error( " {} " , HeedError::InvalidDatabaseTyping) ]
2021-06-10 15:55:22 +02:00
InvalidDatabaseTyping ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
RayonThreadPool ( #[ from ] ThreadPoolBuildError ) ,
#[ error(transparent) ]
SerdeJson ( #[ from ] serde_json ::Error ) ,
#[ error(transparent) ]
Serialization ( #[ from ] SerializationError ) ,
#[ error(transparent) ]
Store ( #[ from ] MdbError ) ,
#[ error(transparent) ]
Utf8 ( #[ from ] str ::Utf8Error ) ,
}
#[ derive(Error, Debug) ]
2021-06-10 15:55:22 +02:00
pub enum SerializationError {
2022-05-02 20:10:25 +02:00
#[ error( " {} " , match .db_name {
Some ( name ) = > format! ( " decoding from the {name} database failed " ) ,
None = > " decoding failed " . to_string ( ) ,
} ) ]
2021-06-10 15:55:22 +02:00
Decoding { db_name : Option < & 'static str > } ,
2022-05-02 20:10:25 +02:00
#[ error( " {} " , match .db_name {
Some ( name ) = > format! ( " encoding into the {name} database failed " ) ,
None = > " encoding failed " . to_string ( ) ,
} ) ]
2021-06-10 15:55:22 +02:00
Encoding { db_name : Option < & 'static str > } ,
2022-05-02 20:10:25 +02:00
#[ error( " number is not a valid finite number " ) ]
2021-06-10 15:55:22 +02:00
InvalidNumberSerialization ,
2021-06-09 17:05:46 +02:00
}
2022-05-02 20:10:25 +02:00
#[ derive(Error, Debug) ]
2021-06-09 17:05:46 +02:00
pub enum FieldIdMapMissingEntry {
2022-05-02 20:10:25 +02:00
#[ error( " unknown field id {field_id} coming from the {process} process " ) ]
2021-06-15 11:10:50 +02:00
FieldId { field_id : FieldId , process : & 'static str } ,
2022-05-02 20:10:25 +02:00
#[ error( " unknown field name {field_name} coming from the {process} process " ) ]
2021-06-15 11:10:50 +02:00
FieldName { field_name : String , process : & 'static str } ,
2021-06-09 17:05:46 +02:00
}
2022-05-02 20:10:25 +02:00
#[ derive(Error, Debug) ]
2021-06-09 17:05:46 +02:00
pub enum UserError {
2022-05-02 20:10:25 +02:00
#[ error( " A document cannot contain more than 65,535 fields. " ) ]
2021-06-10 15:55:22 +02:00
AttributeLimitReached ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
CriterionError ( #[ from ] CriterionError ) ,
#[ error( " Maximum number of documents reached. " ) ]
2021-06-10 15:55:22 +02:00
DocumentLimitReached ,
2022-05-02 20:10:25 +02:00
#[ error(
" Document identifier `{}` is invalid. \
A document identifier can be of type integer or string , \
only composed of alphanumeric characters ( a - z A - Z 0 - 9 ) , hyphens ( - ) and underscores ( _ ) . " , .document_id.to_string()
) ]
2021-06-10 17:31:08 +02:00
InvalidDocumentId { document_id : Value } ,
2022-05-02 20:10:25 +02:00
#[ error( " Invalid facet distribution, the fields `{}` are not set as filterable. " ,
. invalid_facets_name . iter ( ) . map ( AsRef ::as_ref ) . collect ::< Vec < _ > > ( ) . join ( " , " )
) ]
2021-10-28 11:18:32 +02:00
InvalidFacetsDistribution { invalid_facets_name : BTreeSet < String > } ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
InvalidGeoField ( #[ from ] GeoError ) ,
#[ error( " {0} " ) ]
2021-11-04 17:24:55 +01:00
InvalidFilter ( String ) ,
2022-05-02 20:10:25 +02:00
#[ error( " Attribute `{}` is not sortable. {} " ,
. field ,
match . valid_fields . is_empty ( ) {
true = > " This index does not have configured sortable attributes. " . to_string ( ) ,
false = > format! ( " Available sortable attributes are: ` {} `. " ,
valid_fields . iter ( ) . map ( AsRef ::as_ref ) . collect ::< Vec < _ > > ( ) . join ( " , " )
) ,
}
) ]
2021-10-28 11:18:32 +02:00
InvalidSortableAttribute { field : String , valid_fields : BTreeSet < String > } ,
2022-05-02 20:10:25 +02:00
#[ error( " The sort ranking rule must be specified in the ranking rules settings to use the sort parameter at search time. " ) ]
2021-09-07 10:37:57 +02:00
SortRankingRuleMissing ,
2022-05-02 20:10:25 +02:00
#[ error( " The database file is in an invalid state. " ) ]
2021-06-14 16:58:38 +02:00
InvalidStoreFile ,
2022-05-02 20:10:25 +02:00
#[ error( " Maximum database size has been reached. " ) ]
2021-06-23 13:56:13 +02:00
MaxDatabaseSizeReached ,
2022-05-02 20:10:25 +02:00
#[ error( " Document doesn't have a `{}` attribute: `{}`. " , .primary_key, serde_json::to_string(.document).unwrap()) ]
2021-10-26 17:49:35 +02:00
MissingDocumentId { primary_key : String , document : Object } ,
2022-05-02 20:10:25 +02:00
#[ error( " The primary key inference process failed because the engine did not find any fields containing `id` substring in their name. If your document identifier does not contain any `id` substring, you can set the primary key of the index. " ) ]
2021-06-10 15:55:22 +02:00
MissingPrimaryKey ,
2022-05-02 20:10:25 +02:00
#[ error( " There is no more space left on the device. Consider increasing the size of the disk/partition. " ) ]
2021-06-10 15:55:22 +02:00
NoSpaceLeftOnDevice ,
2022-05-02 20:10:25 +02:00
#[ error( " Index already has a primary key: `{0}`. " ) ]
2021-10-26 17:49:35 +02:00
PrimaryKeyCannotBeChanged ( String ) ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
2021-06-14 16:58:38 +02:00
SerdeJson ( serde_json ::Error ) ,
2022-05-02 20:10:25 +02:00
#[ error(transparent) ]
SortError ( #[ from ] SortError ) ,
#[ error( " An unknown internal document id have been used: `{document_id}`. " ) ]
2021-06-14 16:58:38 +02:00
UnknownInternalDocumentId { document_id : DocumentId } ,
2022-05-02 20:10:25 +02:00
#[ 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}`. " ) ]
2022-03-31 14:15:02 +02:00
InvalidMinTypoWordLenSetting ( u8 , u8 ) ,
2021-06-10 15:55:22 +02:00
}
2022-05-02 20:10:25 +02:00
#[ derive(Error, Debug) ]
2022-05-02 19:19:50 +02:00
pub enum GeoError {
2022-05-02 20:10:25 +02:00
#[ error( " Could not find latitude in the document with the id: `{document_id}`. Was expecting a `_geo.lat` field. " ) ]
2022-05-02 19:19:50 +02:00
MissingLatitude { document_id : Value } ,
2022-05-02 20:10:25 +02:00
#[ error( " Could not find longitude in the document with the id: `{document_id}`. Was expecting a `_geo.lng` field. " ) ]
2022-05-02 19:19:50 +02:00
MissingLongitude { document_id : Value } ,
2022-05-02 20:10:25 +02:00
#[ error( " Could not parse latitude in the document with the id: `{document_id}`. Was expecting a number but instead got `{value}`. " ) ]
2022-05-02 19:19:50 +02:00
BadLatitude { document_id : Value , value : Value } ,
2022-05-02 20:10:25 +02:00
#[ error( " Could not parse longitude in the document with the id: `{document_id}`. Was expecting a number but instead got `{value}`. " ) ]
2022-05-02 19:19:50 +02:00
BadLongitude { document_id : Value , value : Value } ,
}
2022-05-02 20:10:25 +02:00
/// A little macro helper to autogenerate From implementation that needs two `Into`.
/// Given the following parameters: `error_from_sub_error!(FieldIdMapMissingEntry => InternalError)`
/// the macro will create the following code:
/// ```ignore
/// impl From<FieldIdMapMissingEntry> for Error {
/// fn from(error: FieldIdMapMissingEntry) -> Error {
/// Error::from(InternalError::from(error))
/// }
/// }
/// ```
macro_rules ! error_from_sub_error {
( ) = > { } ;
( $sub :ty = > $intermediate :ty ) = > {
impl From < $sub > for Error {
fn from ( error : $sub ) -> Error {
Error ::from ( < $intermediate > ::from ( error ) )
}
}
} ;
( $( $sub :ty = > $intermediate :ty $(, ) ? ) , + ) = > {
$( error_from_sub_error! ( $sub = > $intermediate ) ; ) +
} ;
2021-06-10 15:55:22 +02:00
}
2022-05-02 20:10:25 +02:00
error_from_sub_error! {
FieldIdMapMissingEntry = > InternalError ,
fst ::Error = > InternalError ,
str ::Utf8Error = > InternalError ,
ThreadPoolBuildError = > InternalError ,
SerializationError = > InternalError ,
GeoError = > UserError ,
CriterionError = > UserError ,
2022-05-04 14:11:03 +02:00
}
2021-06-16 18:33:33 +02:00
impl < E > From < grenad ::Error < E > > for Error
where
Error : From < E > ,
{
2021-06-14 16:58:38 +02:00
fn from ( error : grenad ::Error < E > ) -> Error {
match error {
grenad ::Error ::Io ( error ) = > Error ::IoError ( error ) ,
grenad ::Error ::Merge ( error ) = > Error ::from ( error ) ,
grenad ::Error ::InvalidCompressionType = > {
Error ::InternalError ( InternalError ::GrenadInvalidCompressionType )
2021-06-16 18:33:33 +02:00
}
2022-02-16 15:28:48 +01:00
grenad ::Error ::InvalidFormatVersion = > {
Error ::InternalError ( InternalError ::GrenadInvalidFormatVersion )
}
2021-06-14 16:58:38 +02:00
}
}
}
impl From < Infallible > for Error {
fn from ( _error : Infallible ) -> Error {
unreachable! ( )
}
}
2021-06-10 15:55:22 +02:00
impl From < HeedError > for Error {
fn from ( error : HeedError ) -> Error {
use self ::Error ::* ;
use self ::InternalError ::* ;
use self ::SerializationError ::* ;
use self ::UserError ::* ;
2021-06-09 17:05:46 +02:00
2021-06-10 15:55:22 +02:00
match error {
HeedError ::Io ( error ) = > Error ::from ( error ) ,
2021-06-15 17:20:33 +02:00
HeedError ::Mdb ( MdbError ::MapFull ) = > UserError ( MaxDatabaseSizeReached ) ,
2021-06-10 15:55:22 +02:00
HeedError ::Mdb ( MdbError ::Invalid ) = > UserError ( InvalidStoreFile ) ,
2021-06-14 16:58:38 +02:00
HeedError ::Mdb ( error ) = > InternalError ( Store ( error ) ) ,
HeedError ::Encoding = > InternalError ( Serialization ( Encoding { db_name : None } ) ) ,
HeedError ::Decoding = > InternalError ( Serialization ( Decoding { db_name : None } ) ) ,
2021-06-10 15:55:22 +02:00
HeedError ::InvalidDatabaseTyping = > InternalError ( InvalidDatabaseTyping ) ,
HeedError ::DatabaseClosing = > InternalError ( DatabaseClosing ) ,
}
}
2021-06-09 17:05:46 +02:00
}
2021-06-10 17:31:08 +02:00
2022-03-04 16:34:03 -03:00
#[ test ]
fn conditionally_lookup_for_error_message ( ) {
let prefix = " Attribute `name` is not sortable. " ;
let messages = vec! [
( BTreeSet ::new ( ) , " This index does not have configured sortable attributes. " ) ,
( BTreeSet ::from ( [ " age " . to_string ( ) ] ) , " Available sortable attributes are: `age`. " ) ,
] ;
for ( list , suffix ) in messages {
let err =
UserError ::InvalidSortableAttribute { field : " name " . to_string ( ) , valid_fields : list } ;
assert_eq! ( err . to_string ( ) , format! ( " {} {} " , prefix , suffix ) ) ;
}
}