2023-01-11 12:33:45 +01:00
use std ::convert ::Infallible ;
use std ::marker ::PhantomData ;
2023-01-12 13:55:53 +01:00
use std ::str ::FromStr ;
2022-12-19 20:50:40 +01:00
use std ::{ fmt , io } ;
2021-03-10 13:46:49 +01:00
2022-10-20 18:00:07 +02:00
use actix_web ::http ::StatusCode ;
use actix_web ::{ self as aweb , HttpResponseBuilder } ;
2022-09-27 16:33:37 +02:00
use aweb ::rt ::task ::JoinError ;
2023-01-02 16:13:44 +01:00
use convert_case ::Casing ;
2023-01-12 13:55:53 +01:00
use deserr ::{ DeserializeError , ErrorKind , IntoValue , MergeWithError , ValueKind , ValuePointerRef } ;
2022-10-04 11:07:14 +02:00
use milli ::heed ::{ Error as HeedError , MdbError } ;
2021-09-27 15:41:14 +02:00
use serde ::{ Deserialize , Serialize } ;
2023-01-12 13:55:53 +01:00
use serde_cs ::vec ::CS ;
use crate ::star_or ::StarOr ;
2021-03-10 13:46:49 +01:00
2023-01-12 15:35:03 +01:00
use self ::deserr_codes ::{
2023-01-12 16:42:50 +01:00
InvalidSwapIndexes , MissingApiKeyActions , MissingApiKeyExpiresAt , MissingApiKeyIndexes ,
MissingIndexUid , MissingSwapIndexes ,
2023-01-12 15:35:03 +01:00
} ;
2023-01-11 12:33:56 +01:00
2021-12-02 16:03:26 +01:00
#[ derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq) ]
#[ serde(rename_all = " camelCase " ) ]
#[ cfg_attr(feature = " test-traits " , derive(proptest_derive::Arbitrary)) ]
pub struct ResponseError {
#[ serde(skip) ]
2022-10-20 18:00:07 +02:00
#[ cfg_attr(feature = " test-traits " , proptest(strategy = " strategy::status_code_strategy() " )) ]
2021-12-02 16:03:26 +01:00
code : StatusCode ,
message : String ,
#[ serde(rename = " code " ) ]
error_code : String ,
#[ serde(rename = " type " ) ]
error_type : String ,
#[ serde(rename = " link " ) ]
error_link : String ,
}
impl ResponseError {
2022-12-20 17:31:13 +01:00
pub fn from_msg ( mut message : String , code : Code ) -> Self {
if code = = Code ::IoError {
message . push_str ( " . This error generally happens when you have no space left on device or when your database doesn't have read or write right. " ) ;
}
2021-12-02 16:03:26 +01:00
Self {
code : code . http ( ) ,
message ,
2023-01-11 12:33:45 +01:00
error_code : code . err_code ( ) . error_name ,
2021-12-02 16:03:26 +01:00
error_type : code . type_ ( ) ,
error_link : code . url ( ) ,
}
}
}
impl fmt ::Display for ResponseError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
self . message . fmt ( f )
}
}
impl std ::error ::Error for ResponseError { }
impl < T > From < T > for ResponseError
where
2023-01-11 12:33:45 +01:00
T : std ::error ::Error + ErrorCode ,
2021-12-02 16:03:26 +01:00
{
fn from ( other : T ) -> Self {
2022-12-20 17:31:13 +01:00
Self ::from_msg ( other . to_string ( ) , other . error_code ( ) )
2021-12-02 16:03:26 +01:00
}
}
impl aweb ::error ::ResponseError for ResponseError {
2022-01-21 21:44:17 +01:00
fn error_response ( & self ) -> aweb ::HttpResponse {
2021-12-02 16:03:26 +01:00
let json = serde_json ::to_vec ( self ) . unwrap ( ) ;
2022-10-20 18:00:07 +02:00
HttpResponseBuilder ::new ( self . status_code ( ) ) . content_type ( " application/json " ) . body ( json )
2021-12-02 16:03:26 +01:00
}
fn status_code ( & self ) -> StatusCode {
self . code
}
}
2023-01-11 12:33:45 +01:00
pub trait ErrorCode {
2021-03-10 13:46:49 +01:00
fn error_code ( & self ) -> Code ;
2022-06-05 04:38:04 +02:00
/// returns the HTTP status code associated with the error
2021-03-10 13:46:49 +01:00
fn http_status ( & self ) -> StatusCode {
self . error_code ( ) . http ( )
}
2022-06-05 04:38:04 +02:00
/// returns the doc url associated with the error
2021-03-10 13:46:49 +01:00
fn error_url ( & self ) -> String {
self . error_code ( ) . url ( )
}
/// returns error name, used as error code
fn error_name ( & self ) -> String {
self . error_code ( ) . name ( )
}
/// return the error type
fn error_type ( & self ) -> String {
self . error_code ( ) . type_ ( )
}
}
#[ allow(clippy::enum_variant_names) ]
enum ErrorType {
InternalError ,
InvalidRequestError ,
AuthenticationError ,
2023-01-05 21:06:50 +01:00
System ,
2021-03-10 13:46:49 +01:00
}
impl fmt ::Display for ErrorType {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
use ErrorType ::* ;
match self {
2021-10-25 14:09:24 +02:00
InternalError = > write! ( f , " internal " ) ,
InvalidRequestError = > write! ( f , " invalid_request " ) ,
2021-10-28 16:57:48 +02:00
AuthenticationError = > write! ( f , " auth " ) ,
2023-01-05 21:06:50 +01:00
System = > write! ( f , " system " ) ,
2021-03-10 13:46:49 +01:00
}
}
}
2023-01-11 12:33:45 +01:00
macro_rules ! make_error_codes {
( $( $code_ident :ident , $err_type :ident , $status :ident ) ; * ) = > {
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
pub enum Code {
$( $code_ident ) , *
}
impl Code {
/// associate a `Code` variant to the actual ErrCode
fn err_code ( & self ) -> ErrCode {
match self {
$(
Code ::$code_ident = > {
ErrCode ::$err_type ( stringify! ( $code_ident ) . to_case ( convert_case ::Case ::Snake ) , StatusCode ::$status )
}
) *
}
2022-12-14 13:00:43 +01:00
}
2023-01-11 12:33:45 +01:00
/// return the HTTP status code associated with the `Code`
fn http ( & self ) -> StatusCode {
self . err_code ( ) . status_code
2022-12-14 13:00:43 +01:00
}
2023-01-11 12:33:45 +01:00
/// return error name, used as error code
fn name ( & self ) -> String {
self . err_code ( ) . error_name . to_string ( )
2022-12-14 13:00:43 +01:00
}
2023-01-11 12:33:45 +01:00
/// return the error type
fn type_ ( & self ) -> String {
self . err_code ( ) . error_type . to_string ( )
2022-12-14 13:00:43 +01:00
}
2023-01-08 11:59:01 +01:00
2023-01-11 12:33:45 +01:00
/// return the doc url associated with the error
fn url ( & self ) -> String {
format! (
" https://docs.meilisearch.com/errors#{} " ,
self . name ( ) . to_case ( convert_case ::Case ::Kebab )
)
2023-01-08 11:59:01 +01:00
}
2021-03-10 13:46:49 +01:00
}
2023-01-11 12:33:45 +01:00
pub mod deserr_codes {
use super ::{ Code , ErrorCode } ;
$(
#[ derive(Default) ]
pub struct $code_ident ;
impl ErrorCode for $code_ident {
fn error_code ( & self ) -> Code {
Code ::$code_ident
}
}
) *
}
2021-03-10 13:46:49 +01:00
}
2023-01-11 12:33:45 +01:00
}
make_error_codes! {
ApiKeyAlreadyExists , invalid , CONFLICT ;
ApiKeyNotFound , invalid , NOT_FOUND ;
BadParameter , invalid , BAD_REQUEST ;
BadRequest , invalid , BAD_REQUEST ;
DatabaseSizeLimitReached , internal , INTERNAL_SERVER_ERROR ;
DocumentNotFound , invalid , NOT_FOUND ;
DumpAlreadyProcessing , invalid , CONFLICT ;
DumpNotFound , invalid , NOT_FOUND ;
DumpProcessFailed , internal , INTERNAL_SERVER_ERROR ;
DuplicateIndexFound , invalid , BAD_REQUEST ;
2023-01-12 09:35:56 +01:00
ImmutableApiKeyUid , invalid , BAD_REQUEST ;
ImmutableApiKeyKey , invalid , BAD_REQUEST ;
ImmutableApiKeyActions , invalid , BAD_REQUEST ;
ImmutableApiKeyIndexes , invalid , BAD_REQUEST ;
ImmutableApiKeyExpiresAt , invalid , BAD_REQUEST ;
ImmutableApiKeyCreatedAt , invalid , BAD_REQUEST ;
ImmutableApiKeyUpdatedAt , invalid , BAD_REQUEST ;
ImmutableIndexUid , invalid , BAD_REQUEST ;
ImmutableIndexCreatedAt , invalid , BAD_REQUEST ;
ImmutableIndexUpdatedAt , invalid , BAD_REQUEST ;
2023-01-11 12:33:45 +01:00
IndexAlreadyExists , invalid , CONFLICT ;
IndexCreationFailed , internal , INTERNAL_SERVER_ERROR ;
IndexNotFound , invalid , NOT_FOUND ;
IndexPrimaryKeyAlreadyExists , invalid , BAD_REQUEST ;
IndexPrimaryKeyNoCandidateFound , invalid , BAD_REQUEST ;
IndexPrimaryKeyMultipleCandidatesFound , invalid , BAD_REQUEST ;
Internal , internal , INTERNAL_SERVER_ERROR ;
InvalidApiKeyActions , invalid , BAD_REQUEST ;
InvalidApiKeyDescription , invalid , BAD_REQUEST ;
InvalidApiKeyExpiresAt , invalid , BAD_REQUEST ;
InvalidApiKeyIndexes , invalid , BAD_REQUEST ;
InvalidApiKeyLimit , invalid , BAD_REQUEST ;
InvalidApiKeyName , invalid , BAD_REQUEST ;
InvalidApiKeyOffset , invalid , BAD_REQUEST ;
InvalidApiKeyUid , invalid , BAD_REQUEST ;
InvalidApiKey , authentication , FORBIDDEN ;
InvalidContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
InvalidDocumentFields , invalid , BAD_REQUEST ;
InvalidDocumentGeoField , invalid , BAD_REQUEST ;
InvalidDocumentId , invalid , BAD_REQUEST ;
InvalidDocumentLimit , invalid , BAD_REQUEST ;
InvalidDocumentOffset , invalid , BAD_REQUEST ;
InvalidIndexLimit , invalid , BAD_REQUEST ;
InvalidIndexOffset , invalid , BAD_REQUEST ;
InvalidIndexPrimaryKey , invalid , BAD_REQUEST ;
InvalidIndexUid , invalid , BAD_REQUEST ;
InvalidMinWordLengthForTypo , invalid , BAD_REQUEST ;
InvalidSearchAttributesToCrop , invalid , BAD_REQUEST ;
InvalidSearchAttributesToHighlight , invalid , BAD_REQUEST ;
InvalidSearchAttributesToRetrieve , invalid , BAD_REQUEST ;
InvalidSearchCropLength , invalid , BAD_REQUEST ;
InvalidSearchCropMarker , invalid , BAD_REQUEST ;
InvalidSearchFacets , invalid , BAD_REQUEST ;
InvalidSearchFilter , invalid , BAD_REQUEST ;
InvalidSearchHighlightPostTag , invalid , BAD_REQUEST ;
InvalidSearchHighlightPreTag , invalid , BAD_REQUEST ;
InvalidSearchHitsPerPage , invalid , BAD_REQUEST ;
InvalidSearchLimit , invalid , BAD_REQUEST ;
InvalidSearchMatchingStrategy , invalid , BAD_REQUEST ;
InvalidSearchOffset , invalid , BAD_REQUEST ;
InvalidSearchPage , invalid , BAD_REQUEST ;
InvalidSearchQ , invalid , BAD_REQUEST ;
InvalidSearchShowMatchesPosition , invalid , BAD_REQUEST ;
InvalidSearchSort , invalid , BAD_REQUEST ;
InvalidSettingsDisplayedAttributes , invalid , BAD_REQUEST ;
InvalidSettingsDistinctAttribute , invalid , BAD_REQUEST ;
InvalidSettingsFaceting , invalid , BAD_REQUEST ;
InvalidSettingsFilterableAttributes , invalid , BAD_REQUEST ;
InvalidSettingsPagination , invalid , BAD_REQUEST ;
InvalidSettingsRankingRules , invalid , BAD_REQUEST ;
InvalidSettingsSearchableAttributes , invalid , BAD_REQUEST ;
InvalidSettingsSortableAttributes , invalid , BAD_REQUEST ;
InvalidSettingsStopWords , invalid , BAD_REQUEST ;
InvalidSettingsSynonyms , invalid , BAD_REQUEST ;
InvalidSettingsTypoTolerance , invalid , BAD_REQUEST ;
InvalidState , internal , INTERNAL_SERVER_ERROR ;
InvalidStoreFile , internal , INTERNAL_SERVER_ERROR ;
InvalidSwapDuplicateIndexFound , invalid , BAD_REQUEST ;
InvalidSwapIndexes , invalid , BAD_REQUEST ;
InvalidTaskAfterEnqueuedAt , invalid , BAD_REQUEST ;
InvalidTaskAfterFinishedAt , invalid , BAD_REQUEST ;
InvalidTaskAfterStartedAt , invalid , BAD_REQUEST ;
InvalidTaskBeforeEnqueuedAt , invalid , BAD_REQUEST ;
InvalidTaskBeforeFinishedAt , invalid , BAD_REQUEST ;
InvalidTaskBeforeStartedAt , invalid , BAD_REQUEST ;
InvalidTaskCanceledBy , invalid , BAD_REQUEST ;
InvalidTaskFrom , invalid , BAD_REQUEST ;
InvalidTaskLimit , invalid , BAD_REQUEST ;
InvalidTaskStatuses , invalid , BAD_REQUEST ;
InvalidTaskTypes , invalid , BAD_REQUEST ;
InvalidTaskUids , invalid , BAD_REQUEST ;
IoError , system , UNPROCESSABLE_ENTITY ;
MalformedPayload , invalid , BAD_REQUEST ;
MaxFieldsLimitExceeded , invalid , BAD_REQUEST ;
MissingApiKeyActions , invalid , BAD_REQUEST ;
MissingApiKeyExpiresAt , invalid , BAD_REQUEST ;
MissingApiKeyIndexes , invalid , BAD_REQUEST ;
MissingAuthorizationHeader , authentication , UNAUTHORIZED ;
MissingContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
MissingDocumentId , invalid , BAD_REQUEST ;
MissingIndexUid , invalid , BAD_REQUEST ;
MissingMasterKey , authentication , UNAUTHORIZED ;
MissingPayload , invalid , BAD_REQUEST ;
2023-01-12 16:42:50 +01:00
MissingSwapIndexes , invalid , BAD_REQUEST ;
2023-01-11 12:33:45 +01:00
MissingTaskFilters , invalid , BAD_REQUEST ;
NoSpaceLeftOnDevice , system , UNPROCESSABLE_ENTITY ;
PayloadTooLarge , invalid , PAYLOAD_TOO_LARGE ;
TaskNotFound , invalid , NOT_FOUND ;
TooManyOpenFiles , system , UNPROCESSABLE_ENTITY ;
UnretrievableDocument , internal , BAD_REQUEST ;
UnretrievableErrorCode , invalid , BAD_REQUEST ;
UnsupportedMediaType , invalid , UNSUPPORTED_MEDIA_TYPE
2021-03-10 13:46:49 +01:00
}
/// Internal structure providing a convenient way to create error codes
struct ErrCode {
status_code : StatusCode ,
error_type : ErrorType ,
2023-01-11 12:33:45 +01:00
error_name : String ,
2021-03-10 13:46:49 +01:00
}
impl ErrCode {
2023-01-11 12:33:45 +01:00
fn authentication ( error_name : String , status_code : StatusCode ) -> ErrCode {
2022-10-20 18:00:07 +02:00
ErrCode { status_code , error_name , error_type : ErrorType ::AuthenticationError }
2021-03-10 13:46:49 +01:00
}
2023-01-11 12:33:45 +01:00
fn internal ( error_name : String , status_code : StatusCode ) -> ErrCode {
2022-10-20 18:00:07 +02:00
ErrCode { status_code , error_name , error_type : ErrorType ::InternalError }
2021-03-10 13:46:49 +01:00
}
2023-01-11 12:33:45 +01:00
fn invalid ( error_name : String , status_code : StatusCode ) -> ErrCode {
2022-10-20 18:00:07 +02:00
ErrCode { status_code , error_name , error_type : ErrorType ::InvalidRequestError }
2021-03-10 13:46:49 +01:00
}
2023-01-05 21:06:50 +01:00
2023-01-11 12:33:45 +01:00
fn system ( error_name : String , status_code : StatusCode ) -> ErrCode {
2023-01-05 21:06:50 +01:00
ErrCode { status_code , error_name , error_type : ErrorType ::System }
}
2021-03-10 13:46:49 +01:00
}
2021-12-02 16:03:26 +01:00
2022-09-27 16:33:37 +02:00
impl ErrorCode for JoinError {
fn error_code ( & self ) -> Code {
Code ::Internal
}
}
2022-10-04 10:38:17 +02:00
impl ErrorCode for milli ::Error {
fn error_code ( & self ) -> Code {
use milli ::{ Error , UserError } ;
match self {
Error ::InternalError ( _ ) = > Code ::Internal ,
2022-12-19 20:50:40 +01:00
Error ::IoError ( e ) = > e . error_code ( ) ,
2022-10-04 10:38:17 +02:00
Error ::UserError ( ref error ) = > {
match error {
// TODO: wait for spec for new error codes.
UserError ::SerdeJson ( _ )
| UserError ::InvalidLmdbOpenOptions
| UserError ::DocumentLimitReached
| UserError ::AccessingSoftDeletedDocument { .. }
| UserError ::UnknownInternalDocumentId { .. } = > Code ::Internal ,
2023-01-11 12:33:45 +01:00
UserError ::InvalidStoreFile = > Code ::InvalidStoreFile ,
2022-10-04 10:38:17 +02:00
UserError ::NoSpaceLeftOnDevice = > Code ::NoSpaceLeftOnDevice ,
UserError ::MaxDatabaseSizeReached = > Code ::DatabaseSizeLimitReached ,
UserError ::AttributeLimitReached = > Code ::MaxFieldsLimitExceeded ,
2023-01-09 18:59:09 +01:00
UserError ::InvalidFilter ( _ ) = > Code ::InvalidSearchFilter ,
2022-10-04 10:38:17 +02:00
UserError ::MissingDocumentId { .. } = > Code ::MissingDocumentId ,
UserError ::InvalidDocumentId { .. } | UserError ::TooManyDocumentIds { .. } = > {
Code ::InvalidDocumentId
}
2023-01-11 12:33:45 +01:00
UserError ::NoPrimaryKeyCandidateFound = > Code ::IndexPrimaryKeyNoCandidateFound ,
2022-12-21 12:02:01 +01:00
UserError ::MultiplePrimaryKeyCandidatesFound { .. } = > {
2023-01-11 12:33:45 +01:00
Code ::IndexPrimaryKeyMultipleCandidatesFound
2022-12-21 12:02:01 +01:00
}
2023-01-11 12:33:45 +01:00
UserError ::PrimaryKeyCannotBeChanged ( _ ) = > Code ::IndexPrimaryKeyAlreadyExists ,
2023-01-09 18:59:09 +01:00
UserError ::SortRankingRuleMissing = > Code ::InvalidSearchSort ,
2022-10-04 10:38:17 +02:00
UserError ::InvalidFacetsDistribution { .. } = > Code ::BadRequest ,
2023-01-09 18:59:09 +01:00
UserError ::InvalidSortableAttribute { .. } = > Code ::InvalidSearchSort ,
2023-01-11 14:31:34 +01:00
UserError ::CriterionError ( _ ) = > Code ::InvalidSettingsRankingRules ,
2023-01-05 21:08:19 +01:00
UserError ::InvalidGeoField { .. } = > Code ::InvalidDocumentGeoField ,
2023-01-09 18:59:09 +01:00
UserError ::SortError ( _ ) = > Code ::InvalidSearchSort ,
2022-10-04 10:38:17 +02:00
UserError ::InvalidMinTypoWordLenSetting ( _ , _ ) = > {
Code ::InvalidMinWordLengthForTypo
}
}
}
}
}
}
2022-12-19 20:50:40 +01:00
impl ErrorCode for file_store ::Error {
fn error_code ( & self ) -> Code {
match self {
Self ::IoError ( e ) = > e . error_code ( ) ,
Self ::PersistError ( e ) = > e . error_code ( ) ,
}
}
}
impl ErrorCode for tempfile ::PersistError {
fn error_code ( & self ) -> Code {
self . error . error_code ( )
}
}
2022-10-04 11:07:14 +02:00
impl ErrorCode for HeedError {
fn error_code ( & self ) -> Code {
match self {
HeedError ::Mdb ( MdbError ::MapFull ) = > Code ::DatabaseSizeLimitReached ,
2023-01-11 12:33:45 +01:00
HeedError ::Mdb ( MdbError ::Invalid ) = > Code ::InvalidStoreFile ,
2022-12-19 20:50:40 +01:00
HeedError ::Io ( e ) = > e . error_code ( ) ,
HeedError ::Mdb ( _ )
2022-10-04 11:07:14 +02:00
| HeedError ::Encoding
| HeedError ::Decoding
| HeedError ::InvalidDatabaseTyping
| HeedError ::DatabaseClosing
| HeedError ::BadOpenOptions = > Code ::Internal ,
}
}
}
2022-12-19 20:50:40 +01:00
impl ErrorCode for io ::Error {
fn error_code ( & self ) -> Code {
match self . raw_os_error ( ) {
Some ( 5 ) = > Code ::IoError ,
Some ( 24 ) = > Code ::TooManyOpenFiles ,
Some ( 28 ) = > Code ::NoSpaceLeftOnDevice ,
2022-12-20 16:32:51 +01:00
_ = > Code ::Internal ,
2022-12-19 20:50:40 +01:00
}
}
}
2022-12-14 13:00:43 +01:00
pub fn unwrap_any < T > ( any : Result < T , T > ) -> T {
match any {
Ok ( any ) = > any ,
Err ( any ) = > any ,
}
}
2021-12-02 16:03:26 +01:00
#[ cfg(feature = " test-traits " ) ]
mod strategy {
use proptest ::strategy ::Strategy ;
use super ::* ;
pub ( super ) fn status_code_strategy ( ) -> impl Strategy < Value = StatusCode > {
( 100 .. 999 u16 ) . prop_map ( | i | StatusCode ::from_u16 ( i ) . unwrap ( ) )
}
}
2021-11-08 18:31:27 +01:00
2023-01-12 13:55:53 +01:00
pub struct DeserrJson ;
pub struct DeserrQueryParam ;
pub type DeserrJsonError < C = deserr_codes ::BadRequest > = DeserrError < DeserrJson , C > ;
pub type DeserrQueryParamError < C = deserr_codes ::BadRequest > = DeserrError < DeserrQueryParam , C > ;
pub struct DeserrError < Format , C : Default + ErrorCode > {
2023-01-11 12:33:56 +01:00
pub msg : String ,
pub code : Code ,
2023-01-12 13:55:53 +01:00
_phantom : PhantomData < ( Format , C ) > ,
2023-01-11 12:33:56 +01:00
}
2023-01-12 13:55:53 +01:00
impl < Format , C : Default + ErrorCode > std ::fmt ::Debug for DeserrError < Format , C > {
2023-01-11 12:33:56 +01:00
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
f . debug_struct ( " DeserrError " ) . field ( " msg " , & self . msg ) . field ( " code " , & self . code ) . finish ( )
}
}
2023-01-12 13:55:53 +01:00
impl < Format , C : Default + ErrorCode > std ::fmt ::Display for DeserrError < Format , C > {
2023-01-11 12:33:56 +01:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
write! ( f , " {} " , self . msg )
}
}
2023-01-12 13:55:53 +01:00
impl < Format , C : Default + ErrorCode > std ::error ::Error for DeserrError < Format , C > { }
impl < Format , C : Default + ErrorCode > ErrorCode for DeserrError < Format , C > {
2023-01-11 12:33:56 +01:00
fn error_code ( & self ) -> Code {
self . code
}
}
2023-01-12 13:55:53 +01:00
impl < Format , C1 : Default + ErrorCode , C2 : Default + ErrorCode >
MergeWithError < DeserrError < Format , C2 > > for DeserrError < Format , C1 >
{
2023-01-11 12:33:56 +01:00
fn merge (
_self_ : Option < Self > ,
2023-01-12 13:55:53 +01:00
other : DeserrError < Format , C2 > ,
2023-01-11 12:33:56 +01:00
_merge_location : ValuePointerRef ,
) -> Result < Self , Self > {
Err ( DeserrError { msg : other . msg , code : other . code , _phantom : PhantomData } )
}
}
2023-01-12 13:55:53 +01:00
impl DeserrJsonError < MissingIndexUid > {
2023-01-11 12:33:56 +01:00
pub fn missing_index_uid ( field : & str , location : ValuePointerRef ) -> Self {
let x = unwrap_any ( Self ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::MissingField { field } ,
location ,
) ) ;
Self { msg : x . msg , code : MissingIndexUid . error_code ( ) , _phantom : PhantomData }
}
}
2023-01-12 15:35:03 +01:00
impl DeserrJsonError < MissingApiKeyActions > {
pub fn missing_api_key_actions ( field : & str , location : ValuePointerRef ) -> Self {
let x = unwrap_any ( Self ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::MissingField { field } ,
location ,
) ) ;
Self { msg : x . msg , code : MissingApiKeyActions . error_code ( ) , _phantom : PhantomData }
}
}
impl DeserrJsonError < MissingApiKeyExpiresAt > {
pub fn missing_api_key_expires_at ( field : & str , location : ValuePointerRef ) -> Self {
let x = unwrap_any ( Self ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::MissingField { field } ,
location ,
) ) ;
Self { msg : x . msg , code : MissingApiKeyExpiresAt . error_code ( ) , _phantom : PhantomData }
}
}
impl DeserrJsonError < MissingApiKeyIndexes > {
pub fn missing_api_key_indexes ( field : & str , location : ValuePointerRef ) -> Self {
let x = unwrap_any ( Self ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::MissingField { field } ,
location ,
) ) ;
Self { msg : x . msg , code : MissingApiKeyIndexes . error_code ( ) , _phantom : PhantomData }
}
}
impl DeserrJsonError < InvalidSwapIndexes > {
pub fn missing_swap_indexes_indexes ( field : & str , location : ValuePointerRef ) -> Self {
let x = unwrap_any ( Self ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::MissingField { field } ,
location ,
) ) ;
2023-01-12 16:42:50 +01:00
Self { msg : x . msg , code : MissingSwapIndexes . error_code ( ) , _phantom : PhantomData }
2023-01-12 15:35:03 +01:00
}
}
2023-01-11 12:33:56 +01:00
2023-01-12 13:55:53 +01:00
// if the error happened in the root, then an empty string is returned.
pub fn location_json_description ( location : ValuePointerRef , article : & str ) -> String {
fn rec ( location : ValuePointerRef ) -> String {
match location {
ValuePointerRef ::Origin = > String ::new ( ) ,
ValuePointerRef ::Key { key , prev } = > rec ( * prev ) + " . " + key ,
ValuePointerRef ::Index { index , prev } = > format! ( " {} [ {index} ] " , rec ( * prev ) ) ,
}
}
match location {
ValuePointerRef ::Origin = > String ::new ( ) ,
_ = > {
format! ( " {article} ` {} ` " , rec ( location ) )
}
}
}
fn value_kinds_description_json ( kinds : & [ ValueKind ] ) -> String {
fn order ( kind : & ValueKind ) -> u8 {
match kind {
ValueKind ::Null = > 0 ,
ValueKind ::Boolean = > 1 ,
ValueKind ::Integer = > 2 ,
ValueKind ::NegativeInteger = > 3 ,
ValueKind ::Float = > 4 ,
ValueKind ::String = > 5 ,
ValueKind ::Sequence = > 6 ,
ValueKind ::Map = > 7 ,
}
}
fn single_description ( kind : & ValueKind ) -> & 'static str {
match kind {
ValueKind ::Null = > " null " ,
ValueKind ::Boolean = > " a boolean " ,
ValueKind ::Integer = > " a positive integer " ,
ValueKind ::NegativeInteger = > " an integer " ,
ValueKind ::Float = > " a number " ,
ValueKind ::String = > " a string " ,
ValueKind ::Sequence = > " an array " ,
ValueKind ::Map = > " an object " ,
}
}
fn description_rec ( kinds : & [ ValueKind ] , count_items : & mut usize , message : & mut String ) {
let ( msg_part , rest ) : ( _ , & [ ValueKind ] ) = match kinds {
[ ] = > ( String ::new ( ) , & [ ] ) ,
[ ValueKind ::Integer | ValueKind ::NegativeInteger , ValueKind ::Float , rest @ .. ] = > {
( " a number " . to_owned ( ) , rest )
}
[ ValueKind ::Integer , ValueKind ::NegativeInteger , ValueKind ::Float , rest @ .. ] = > {
( " a number " . to_owned ( ) , rest )
}
[ ValueKind ::Integer , ValueKind ::NegativeInteger , rest @ .. ] = > {
( " an integer " . to_owned ( ) , rest )
}
[ a ] = > ( single_description ( a ) . to_owned ( ) , & [ ] ) ,
[ a , rest @ .. ] = > ( single_description ( a ) . to_owned ( ) , rest ) ,
} ;
if rest . is_empty ( ) {
if * count_items = = 0 {
message . push_str ( & msg_part ) ;
} else if * count_items = = 1 {
message . push_str ( & format! ( " or {msg_part} " ) ) ;
} else {
message . push_str ( & format! ( " , or {msg_part} " ) ) ;
}
} else {
if * count_items = = 0 {
message . push_str ( & msg_part ) ;
} else {
message . push_str ( & format! ( " , {msg_part} " ) ) ;
}
* count_items + = 1 ;
description_rec ( rest , count_items , message ) ;
}
}
let mut kinds = kinds . to_owned ( ) ;
kinds . sort_by_key ( order ) ;
kinds . dedup ( ) ;
if kinds . is_empty ( ) {
" a different value " . to_owned ( )
} else {
let mut message = String ::new ( ) ;
description_rec ( kinds . as_slice ( ) , & mut 0 , & mut message ) ;
message
}
}
fn value_description_with_kind_json ( v : & serde_json ::Value ) -> String {
match v . kind ( ) {
ValueKind ::Null = > " null " . to_owned ( ) ,
kind = > {
format! (
" {}: `{}` " ,
value_kinds_description_json ( & [ kind ] ) ,
serde_json ::to_string ( v ) . unwrap ( )
)
}
}
}
impl < C : Default + ErrorCode > deserr ::DeserializeError for DeserrJsonError < C > {
fn error < V : IntoValue > (
_self_ : Option < Self > ,
error : deserr ::ErrorKind < V > ,
location : ValuePointerRef ,
) -> Result < Self , Self > {
let mut message = String ::new ( ) ;
message . push_str ( & match error {
ErrorKind ::IncorrectValueKind { actual , accepted } = > {
let expected = value_kinds_description_json ( accepted ) ;
// if we're not able to get the value as a string then we print nothing.
let received = value_description_with_kind_json ( & serde_json ::Value ::from ( actual ) ) ;
let location = location_json_description ( location , " at " ) ;
format! ( " Invalid value type {location} : expected {expected} , but found {received} " )
}
ErrorKind ::MissingField { field } = > {
// serde_json original message:
// Json deserialize error: missing field `lol` at line 1 column 2
let location = location_json_description ( location , " inside " ) ;
format! ( " Missing field ` {field} ` {location} " )
}
ErrorKind ::UnknownKey { key , accepted } = > {
let location = location_json_description ( location , " inside " ) ;
format! (
" Unknown field `{}`{location}: expected one of {} " ,
key ,
accepted
. iter ( )
. map ( | accepted | format! ( " ` {} ` " , accepted ) )
. collect ::< Vec < String > > ( )
. join ( " , " )
)
}
ErrorKind ::UnknownValue { value , accepted } = > {
let location = location_json_description ( location , " at " ) ;
format! (
" Unknown value `{}`{location}: expected one of {} " ,
value ,
accepted
. iter ( )
. map ( | accepted | format! ( " ` {} ` " , accepted ) )
. collect ::< Vec < String > > ( )
. join ( " , " ) ,
)
}
ErrorKind ::Unexpected { msg } = > {
let location = location_json_description ( location , " at " ) ;
// serde_json original message:
// The json payload provided is malformed. `trailing characters at line 1 column 19`.
format! ( " Invalid value {location} : {msg} " )
}
} ) ;
Err ( DeserrJsonError {
msg : message ,
code : C ::default ( ) . error_code ( ) ,
_phantom : PhantomData ,
} )
}
}
// if the error happened in the root, then an empty string is returned.
pub fn location_query_param_description ( location : ValuePointerRef , article : & str ) -> String {
fn rec ( location : ValuePointerRef ) -> String {
match location {
ValuePointerRef ::Origin = > String ::new ( ) ,
ValuePointerRef ::Key { key , prev } = > {
if matches! ( prev , ValuePointerRef ::Origin ) {
key . to_owned ( )
} else {
rec ( * prev ) + " . " + key
}
}
ValuePointerRef ::Index { index , prev } = > format! ( " {} [ {index} ] " , rec ( * prev ) ) ,
}
}
match location {
ValuePointerRef ::Origin = > String ::new ( ) ,
_ = > {
format! ( " {article} ` {} ` " , rec ( location ) )
}
}
}
impl < C : Default + ErrorCode > deserr ::DeserializeError for DeserrQueryParamError < C > {
2023-01-11 12:33:56 +01:00
fn error < V : IntoValue > (
_self_ : Option < Self > ,
error : deserr ::ErrorKind < V > ,
location : ValuePointerRef ,
) -> Result < Self , Self > {
2023-01-12 13:55:53 +01:00
let mut message = String ::new ( ) ;
message . push_str ( & match error {
ErrorKind ::IncorrectValueKind { actual , accepted } = > {
let expected = value_kinds_description_query_param ( accepted ) ;
// if we're not able to get the value as a string then we print nothing.
let received = value_description_with_kind_query_param ( actual ) ;
let location = location_query_param_description ( location , " for parameter " ) ;
format! ( " Invalid value type {location} : expected {expected} , but found {received} " )
}
ErrorKind ::MissingField { field } = > {
// serde_json original message:
// Json deserialize error: missing field `lol` at line 1 column 2
let location = location_query_param_description ( location , " inside " ) ;
format! ( " Missing parameter ` {field} ` {location} " )
}
ErrorKind ::UnknownKey { key , accepted } = > {
let location = location_query_param_description ( location , " inside " ) ;
format! (
" Unknown parameter `{}`{location}: expected one of {} " ,
key ,
accepted
. iter ( )
. map ( | accepted | format! ( " ` {} ` " , accepted ) )
. collect ::< Vec < String > > ( )
. join ( " , " )
)
}
ErrorKind ::UnknownValue { value , accepted } = > {
let location = location_query_param_description ( location , " for parameter " ) ;
format! (
" Unknown value `{}`{location}: expected one of {} " ,
value ,
accepted
. iter ( )
. map ( | accepted | format! ( " ` {} ` " , accepted ) )
. collect ::< Vec < String > > ( )
. join ( " , " ) ,
)
}
ErrorKind ::Unexpected { msg } = > {
let location = location_query_param_description ( location , " in parameter " ) ;
// serde_json original message:
// The json payload provided is malformed. `trailing characters at line 1 column 19`.
format! ( " Invalid value {location} : {msg} " )
}
} ) ;
2023-01-11 12:33:56 +01:00
2023-01-12 13:55:53 +01:00
Err ( DeserrQueryParamError {
msg : message ,
code : C ::default ( ) . error_code ( ) ,
_phantom : PhantomData ,
} )
2023-01-11 12:33:56 +01:00
}
}
2023-01-12 13:55:53 +01:00
fn value_kinds_description_query_param ( _accepted : & [ ValueKind ] ) -> String {
" a string " . to_owned ( )
}
fn value_description_with_kind_query_param < V : IntoValue > ( actual : deserr ::Value < V > ) -> String {
match actual {
deserr ::Value ::Null = > " null " . to_owned ( ) ,
deserr ::Value ::Boolean ( x ) = > format! ( " a boolean: ` {x} ` " ) ,
deserr ::Value ::Integer ( x ) = > format! ( " an integer: ` {x} ` " ) ,
deserr ::Value ::NegativeInteger ( x ) = > {
format! ( " an integer: ` {x} ` " )
}
deserr ::Value ::Float ( x ) = > {
format! ( " a number: ` {x} ` " )
}
deserr ::Value ::String ( x ) = > {
format! ( " a string: ` {x} ` " )
}
deserr ::Value ::Sequence ( _ ) = > " multiple values " . to_owned ( ) ,
deserr ::Value ::Map ( _ ) = > " multiple parameters " . to_owned ( ) ,
}
}
#[ derive(Debug) ]
pub struct DetailedParseIntError ( String ) ;
impl fmt ::Display for DetailedParseIntError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
write! ( f , " could not parse `{}` as a positive integer " , self . 0 )
}
}
impl std ::error ::Error for DetailedParseIntError { }
pub fn parse_u32_query_param ( x : String ) -> Result < u32 , TakeErrorMessage < DetailedParseIntError > > {
x . parse ::< u32 > ( ) . map_err ( | _e | TakeErrorMessage ( DetailedParseIntError ( x . to_owned ( ) ) ) )
}
pub fn parse_usize_query_param (
x : String ,
) -> Result < usize , TakeErrorMessage < DetailedParseIntError > > {
x . parse ::< usize > ( ) . map_err ( | _e | TakeErrorMessage ( DetailedParseIntError ( x . to_owned ( ) ) ) )
}
pub fn parse_option_usize_query_param (
s : Option < String > ,
) -> Result < Option < usize > , TakeErrorMessage < DetailedParseIntError > > {
if let Some ( s ) = s {
parse_usize_query_param ( s ) . map ( Some )
} else {
Ok ( None )
}
}
pub fn parse_option_u32_query_param (
s : Option < String > ,
) -> Result < Option < u32 > , TakeErrorMessage < DetailedParseIntError > > {
if let Some ( s ) = s {
parse_u32_query_param ( s ) . map ( Some )
} else {
Ok ( None )
}
}
pub fn parse_option_vec_u32_query_param (
s : Option < serde_cs ::vec ::CS < String > > ,
) -> Result < Option < Vec < u32 > > , TakeErrorMessage < DetailedParseIntError > > {
if let Some ( s ) = s {
s . into_iter ( )
. map ( parse_u32_query_param )
. collect ::< Result < Vec < u32 > , TakeErrorMessage < DetailedParseIntError > > > ( )
. map ( Some )
} else {
Ok ( None )
}
}
pub fn parse_option_cs_star_or < T : FromStr > (
s : Option < CS < StarOr < String > > > ,
) -> Result < Option < Vec < T > > , TakeErrorMessage < T ::Err > > {
if let Some ( s ) = s . and_then ( fold_star_or ) as Option < Vec < String > > {
s . into_iter ( )
. map ( | s | T ::from_str ( & s ) )
. collect ::< Result < Vec < T > , T ::Err > > ( )
. map_err ( TakeErrorMessage )
. map ( Some )
} else {
Ok ( None )
}
}
/// Extracts the raw values from the `StarOr` types and
/// return None if a `StarOr::Star` is encountered.
pub fn fold_star_or < T , O > ( content : impl IntoIterator < Item = StarOr < T > > ) -> Option < O >
where
O : FromIterator < T > ,
{
content
. into_iter ( )
. map ( | value | match value {
StarOr ::Star = > None ,
StarOr ::Other ( val ) = > Some ( val ) ,
} )
. collect ( )
}
2023-01-11 12:33:56 +01:00
pub struct TakeErrorMessage < T > ( pub T ) ;
2023-01-12 13:55:53 +01:00
impl < C : Default + ErrorCode , T > MergeWithError < TakeErrorMessage < T > > for DeserrJsonError < C >
2023-01-11 12:33:56 +01:00
where
T : std ::error ::Error ,
{
fn merge (
_self_ : Option < Self > ,
other : TakeErrorMessage < T > ,
merge_location : ValuePointerRef ,
) -> Result < Self , Self > {
2023-01-12 13:55:53 +01:00
DeserrJsonError ::error ::< Infallible > (
None ,
deserr ::ErrorKind ::Unexpected { msg : other . 0. to_string ( ) } ,
merge_location ,
)
}
}
impl < C : Default + ErrorCode , T > MergeWithError < TakeErrorMessage < T > > for DeserrQueryParamError < C >
where
T : std ::error ::Error ,
{
fn merge (
_self_ : Option < Self > ,
other : TakeErrorMessage < T > ,
merge_location : ValuePointerRef ,
) -> Result < Self , Self > {
DeserrQueryParamError ::error ::< Infallible > (
2023-01-11 12:33:56 +01:00
None ,
deserr ::ErrorKind ::Unexpected { msg : other . 0. to_string ( ) } ,
merge_location ,
)
}
}
2021-11-08 18:31:27 +01:00
#[ macro_export ]
macro_rules ! internal_error {
( $target :ty : $( $other :path ) , * ) = > {
$(
impl From < $other > for $target {
fn from ( other : $other ) -> Self {
Self ::Internal ( Box ::new ( other ) )
}
}
) *
}
}
2023-01-12 13:55:53 +01:00
#[ cfg(test) ]
mod tests {
use deserr ::ValueKind ;
use crate ::error ::value_kinds_description_json ;
#[ test ]
fn test_value_kinds_description_json ( ) {
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ] ) , @ " a different value " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Boolean ] ) , @ " a boolean " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer ] ) , @ " a positive integer " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::NegativeInteger ] ) , @ " an integer " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer ] ) , @ " a positive integer " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::String ] ) , @ " a string " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Sequence ] ) , @ " an array " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Map ] ) , @ " an object " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer , ValueKind ::Boolean ] ) , @ " a boolean or a positive integer " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Null , ValueKind ::Integer ] ) , @ " null or a positive integer " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Sequence , ValueKind ::NegativeInteger ] ) , @ " an integer or an array " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer , ValueKind ::Float ] ) , @ " a number " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer , ValueKind ::Float , ValueKind ::NegativeInteger ] ) , @ " a number " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Integer , ValueKind ::Float , ValueKind ::NegativeInteger , ValueKind ::Null ] ) , @ " null or a number " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Boolean , ValueKind ::Integer , ValueKind ::Float , ValueKind ::NegativeInteger , ValueKind ::Null ] ) , @ " null, a boolean, or a number " ) ;
insta ::assert_display_snapshot! ( value_kinds_description_json ( & [ ValueKind ::Null , ValueKind ::Boolean , ValueKind ::Integer , ValueKind ::Float , ValueKind ::NegativeInteger , ValueKind ::Null ] ) , @ " null, a boolean, or a number " ) ;
}
}