2021-10-28 17:18:32 +08:00
use std ::collections ::BTreeSet ;
2021-06-14 22:58:38 +08:00
use std ::convert ::Infallible ;
2022-05-03 02:10:25 +08:00
use std ::{ io , str } ;
2021-06-09 23:05:46 +08:00
2021-06-17 00:33:33 +08:00
use heed ::{ Error as HeedError , MdbError } ;
2021-06-14 22:58:38 +08:00
use rayon ::ThreadPoolBuildError ;
2021-06-10 23:31:08 +08:00
use serde_json ::{ Map , Value } ;
2022-05-03 02:10:25 +08:00
use thiserror ::Error ;
2021-06-10 23:31:08 +08:00
2021-09-28 01:07:22 +08:00
use crate ::{ CriterionError , DocumentId , FieldId , SortError } ;
2021-06-10 21:55:22 +08:00
2021-06-10 23:31:08 +08:00
pub type Object = Map < String , Value > ;
2021-06-09 23:05:46 +08:00
2021-09-02 21:57:40 +08:00
pub fn is_reserved_keyword ( keyword : & str ) -> bool {
2021-09-20 23:21:02 +08:00
[ " _geo " , " _geoDistance " , " _geoPoint " , " _geoRadius " ] . contains ( & keyword )
2021-09-02 21:57:40 +08:00
}
2022-05-03 02:10:25 +08:00
#[ derive(Error, Debug) ]
2021-06-09 23:05:46 +08:00
pub enum Error {
2022-05-03 02:10:25 +08:00
#[ error( " internal: {0}. " ) ]
InternalError ( #[ from ] InternalError ) ,
#[ error(transparent) ]
IoError ( #[ from ] io ::Error ) ,
#[ error(transparent) ]
UserError ( #[ from ] UserError ) ,
2021-06-09 23:05:46 +08:00
}
2022-05-03 02:10:25 +08:00
#[ derive(Error, Debug) ]
2021-06-09 23:05:46 +08:00
pub enum InternalError {
2022-05-03 02:10:25 +08:00
#[ error( " {} " , HeedError::DatabaseClosing) ]
2021-06-14 22:58:38 +08:00
DatabaseClosing ,
2022-05-03 02:10:25 +08:00
#[ error( " Missing {} in the {db_name} database. " , key.unwrap_or( " key " )) ]
2021-06-10 23:31:08 +08:00
DatabaseMissingEntry { db_name : & 'static str , key : Option < & 'static str > } ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
FieldIdMapMissingEntry ( #[ from ] FieldIdMapMissingEntry ) ,
#[ error( " Missing {key} in the field id mapping. " ) ]
2022-03-24 00:28:41 +08:00
FieldIdMappingMissingEntry { key : FieldId } ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
Fst ( #[ from ] fst ::Error ) ,
#[ error( " Invalid compression type have been specified to grenad. " ) ]
2021-06-14 22:58:38 +08:00
GrenadInvalidCompressionType ,
2022-05-03 02:10:25 +08:00
#[ error( " Invalid grenad file with an invalid version format. " ) ]
2022-02-16 22:28:48 +08:00
GrenadInvalidFormatVersion ,
2022-05-03 02:10:25 +08:00
#[ error( " Invalid merge while processing {process}. " ) ]
2021-06-10 23:31:08 +08:00
IndexingMergingKeys { process : & 'static str } ,
2022-05-03 02:10:25 +08:00
#[ error( " {} " , HeedError::InvalidDatabaseTyping) ]
2021-06-10 21:55:22 +08:00
InvalidDatabaseTyping ,
2022-05-03 02:10:25 +08: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 21:55:22 +08:00
pub enum SerializationError {
2022-05-03 02:10:25 +08:00
#[ error( " {} " , match .db_name {
Some ( name ) = > format! ( " decoding from the {name} database failed " ) ,
None = > " decoding failed " . to_string ( ) ,
} ) ]
2021-06-10 21:55:22 +08:00
Decoding { db_name : Option < & 'static str > } ,
2022-05-03 02:10:25 +08:00
#[ error( " {} " , match .db_name {
Some ( name ) = > format! ( " encoding into the {name} database failed " ) ,
None = > " encoding failed " . to_string ( ) ,
} ) ]
2021-06-10 21:55:22 +08:00
Encoding { db_name : Option < & 'static str > } ,
2022-05-03 02:10:25 +08:00
#[ error( " number is not a valid finite number " ) ]
2021-06-10 21:55:22 +08:00
InvalidNumberSerialization ,
2021-06-09 23:05:46 +08:00
}
2022-05-03 02:10:25 +08:00
#[ derive(Error, Debug) ]
2021-06-09 23:05:46 +08:00
pub enum FieldIdMapMissingEntry {
2022-05-03 02:10:25 +08:00
#[ error( " unknown field id {field_id} coming from the {process} process " ) ]
2021-06-15 17:10:50 +08:00
FieldId { field_id : FieldId , process : & 'static str } ,
2022-05-03 02:10:25 +08:00
#[ error( " unknown field name {field_name} coming from the {process} process " ) ]
2021-06-15 17:10:50 +08:00
FieldName { field_name : String , process : & 'static str } ,
2021-06-09 23:05:46 +08:00
}
2022-05-03 02:10:25 +08:00
#[ derive(Error, Debug) ]
2021-06-09 23:05:46 +08:00
pub enum UserError {
2022-07-04 20:59:11 +08:00
#[ error( " A soft deleted internal document id have been used: `{document_id}`. " ) ]
AccessingSoftDeletedDocument { document_id : DocumentId } ,
2022-05-03 02:10:25 +08:00
#[ error( " A document cannot contain more than 65,535 fields. " ) ]
2021-06-10 21:55:22 +08:00
AttributeLimitReached ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
CriterionError ( #[ from ] CriterionError ) ,
#[ error( " Maximum number of documents reached. " ) ]
2021-06-10 21:55:22 +08:00
DocumentLimitReached ,
2022-05-03 02:10:25 +08: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 23:31:08 +08:00
InvalidDocumentId { document_id : Value } ,
2022-05-03 02:10:25 +08: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 17:18:32 +08:00
InvalidFacetsDistribution { invalid_facets_name : BTreeSet < String > } ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
InvalidGeoField ( #[ from ] GeoError ) ,
#[ error( " {0} " ) ]
2021-11-05 00:24:55 +08:00
InvalidFilter ( String ) ,
2022-05-03 02:10:25 +08: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 17:18:32 +08:00
InvalidSortableAttribute { field : String , valid_fields : BTreeSet < String > } ,
2022-05-03 02:10:25 +08: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 16:37:57 +08:00
SortRankingRuleMissing ,
2022-05-03 02:10:25 +08:00
#[ error( " The database file is in an invalid state. " ) ]
2021-06-14 22:58:38 +08:00
InvalidStoreFile ,
2022-05-03 02:10:25 +08:00
#[ error( " Maximum database size has been reached. " ) ]
2021-06-23 19:56:13 +08:00
MaxDatabaseSizeReached ,
2022-05-03 02:10:25 +08:00
#[ error( " Document doesn't have a `{}` attribute: `{}`. " , .primary_key, serde_json::to_string(.document).unwrap()) ]
2021-10-26 23:49:35 +08:00
MissingDocumentId { primary_key : String , document : Object } ,
2022-05-03 02:10:25 +08: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 21:55:22 +08:00
MissingPrimaryKey ,
2022-05-03 02:10:25 +08:00
#[ error( " There is no more space left on the device. Consider increasing the size of the disk/partition. " ) ]
2021-06-10 21:55:22 +08:00
NoSpaceLeftOnDevice ,
2022-05-03 02:10:25 +08:00
#[ error( " Index already has a primary key: `{0}`. " ) ]
2021-10-26 23:49:35 +08:00
PrimaryKeyCannotBeChanged ( String ) ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
2021-06-14 22:58:38 +08:00
SerdeJson ( serde_json ::Error ) ,
2022-05-03 02:10:25 +08:00
#[ error(transparent) ]
SortError ( #[ from ] SortError ) ,
#[ error( " An unknown internal document id have been used: `{document_id}`. " ) ]
2021-06-14 22:58:38 +08:00
UnknownInternalDocumentId { document_id : DocumentId } ,
2022-05-03 02:10:25 +08: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 20:15:02 +08:00
InvalidMinTypoWordLenSetting ( u8 , u8 ) ,
2021-06-10 21:55:22 +08:00
}
2022-05-03 02:10:25 +08:00
#[ derive(Error, Debug) ]
2022-05-03 01:19:50 +08:00
pub enum GeoError {
2022-06-15 00:12:15 +08:00
#[ 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}`. " ) ]
NotAnObject { document_id : Value , value : Value } ,
#[ error( " Could not find latitude nor longitude in the document with the id: `{document_id}`. Was expecting `_geo.lat` and `_geo.lng` fields. " ) ]
MissingLatitudeAndLongitude { document_id : Value } ,
2022-05-03 02:10:25 +08:00
#[ error( " Could not find latitude in the document with the id: `{document_id}`. Was expecting a `_geo.lat` field. " ) ]
2022-05-03 01:19:50 +08:00
MissingLatitude { document_id : Value } ,
2022-05-03 02:10:25 +08:00
#[ error( " Could not find longitude in the document with the id: `{document_id}`. Was expecting a `_geo.lng` field. " ) ]
2022-05-03 01:19:50 +08:00
MissingLongitude { document_id : Value } ,
2022-06-15 00:12:15 +08:00
#[ error( " Could not parse latitude nor longitude in the document with the id: `{document_id}`. Was expecting a number but instead got `{lat}` and `{lng}`. " ) ]
BadLatitudeAndLongitude { document_id : Value , lat : Value , lng : Value } ,
2022-05-03 02:10:25 +08:00
#[ error( " Could not parse latitude in the document with the id: `{document_id}`. Was expecting a number but instead got `{value}`. " ) ]
2022-05-03 01:19:50 +08:00
BadLatitude { document_id : Value , value : Value } ,
2022-05-03 02:10:25 +08:00
#[ error( " Could not parse longitude in the document with the id: `{document_id}`. Was expecting a number but instead got `{value}`. " ) ]
2022-05-03 01:19:50 +08:00
BadLongitude { document_id : Value , value : Value } ,
}
2022-05-03 02:10:25 +08: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 21:55:22 +08:00
}
2022-05-03 02:10:25 +08:00
error_from_sub_error! {
FieldIdMapMissingEntry = > InternalError ,
fst ::Error = > InternalError ,
str ::Utf8Error = > InternalError ,
ThreadPoolBuildError = > InternalError ,
SerializationError = > InternalError ,
GeoError = > UserError ,
CriterionError = > UserError ,
2022-05-04 20:11:03 +08:00
}
2021-06-17 00:33:33 +08:00
impl < E > From < grenad ::Error < E > > for Error
where
Error : From < E > ,
{
2021-06-14 22:58:38 +08: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-17 00:33:33 +08:00
}
2022-02-16 22:28:48 +08:00
grenad ::Error ::InvalidFormatVersion = > {
Error ::InternalError ( InternalError ::GrenadInvalidFormatVersion )
}
2021-06-14 22:58:38 +08:00
}
}
}
impl From < Infallible > for Error {
fn from ( _error : Infallible ) -> Error {
unreachable! ( )
}
}
2021-06-10 21:55:22 +08: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 23:05:46 +08:00
2021-06-10 21:55:22 +08:00
match error {
HeedError ::Io ( error ) = > Error ::from ( error ) ,
2021-06-15 23:20:33 +08:00
HeedError ::Mdb ( MdbError ::MapFull ) = > UserError ( MaxDatabaseSizeReached ) ,
2021-06-10 21:55:22 +08:00
HeedError ::Mdb ( MdbError ::Invalid ) = > UserError ( InvalidStoreFile ) ,
2021-06-14 22:58:38 +08: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 21:55:22 +08:00
HeedError ::InvalidDatabaseTyping = > InternalError ( InvalidDatabaseTyping ) ,
HeedError ::DatabaseClosing = > InternalError ( DatabaseClosing ) ,
}
}
2021-06-09 23:05:46 +08:00
}
2021-06-10 23:31:08 +08:00
2022-03-05 03:34:03 +08: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 ) ) ;
}
}