2021-09-22 16:59:23 +02:00
//! This module provides the `AscDesc` type and defines all the errors related to this type.
2021-09-22 15:18:39 +02:00
use std ::fmt ;
use std ::str ::FromStr ;
use serde ::{ Deserialize , Serialize } ;
use crate ::error ::is_reserved_keyword ;
2021-09-27 19:07:22 +02:00
use crate ::{ CriterionError , Error , UserError } ;
2021-09-22 15:18:39 +02:00
/// This error type is never supposed to be shown to the end user.
/// You must always cast it to a sort error or a criterion error.
#[ derive(Debug) ]
pub enum AscDescError {
2021-10-07 15:42:08 +02:00
BadLat ,
BadLng ,
2021-09-22 15:18:39 +02:00
InvalidSyntax { name : String } ,
ReservedKeyword { name : String } ,
}
impl fmt ::Display for AscDescError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
2021-10-07 15:42:08 +02:00
Self ::BadLat = > {
write! ( f , " Latitude must be contained between -90 and 90 degrees. " , )
}
Self ::BadLng = > {
write! ( f , " Longitude must be contained between -180 and 180 degrees. " , )
}
2021-09-22 15:18:39 +02:00
Self ::InvalidSyntax { name } = > {
2021-09-30 12:50:40 +02:00
write! ( f , " invalid asc/desc syntax for {}. " , name )
2021-09-22 15:18:39 +02:00
}
Self ::ReservedKeyword { name } = > {
write! (
f ,
2021-09-30 12:50:40 +02:00
" {} is a reserved keyword and thus can't be used as a asc/desc rule. " ,
2021-09-22 15:18:39 +02:00
name
)
}
}
}
}
impl From < AscDescError > for CriterionError {
fn from ( error : AscDescError ) -> Self {
match error {
2021-10-07 15:42:08 +02:00
AscDescError ::BadLat | AscDescError ::BadLng = > {
CriterionError ::ReservedNameForSort { name : " _geoPoint " . to_string ( ) }
}
2021-09-22 15:18:39 +02:00
AscDescError ::InvalidSyntax { name } = > CriterionError ::InvalidName { name } ,
AscDescError ::ReservedKeyword { name } if name . starts_with ( " _geoPoint " ) = > {
CriterionError ::ReservedNameForSort { name : " _geoPoint " . to_string ( ) }
}
AscDescError ::ReservedKeyword { name } if name . starts_with ( " _geoRadius " ) = > {
CriterionError ::ReservedNameForFilter { name : " _geoRadius " . to_string ( ) }
}
2021-09-22 16:59:23 +02:00
AscDescError ::ReservedKeyword { name } = > CriterionError ::ReservedName { name } ,
2021-09-22 15:18:39 +02:00
}
}
}
#[ derive(Debug, Serialize, Deserialize, Clone, PartialEq) ]
pub enum Member {
Field ( String ) ,
Geo ( [ f64 ; 2 ] ) ,
}
impl FromStr for Member {
type Err = AscDescError ;
fn from_str ( text : & str ) -> Result < Member , Self ::Err > {
match text . strip_prefix ( " _geoPoint( " ) . and_then ( | text | text . strip_suffix ( " ) " ) ) {
Some ( point ) = > {
2021-10-07 15:42:08 +02:00
let ( lat , lng ) = point
2021-09-22 15:18:39 +02:00
. split_once ( ',' )
. ok_or_else ( | | AscDescError ::ReservedKeyword { name : text . to_string ( ) } )
2021-10-07 15:42:08 +02:00
. and_then ( | ( lat , lng ) | {
2021-09-22 15:18:39 +02:00
lat . trim ( )
. parse ( )
2021-10-07 15:42:08 +02:00
. and_then ( | lat | lng . trim ( ) . parse ( ) . map ( | lng | ( lat , lng ) ) )
2021-09-22 15:18:39 +02:00
. map_err ( | _ | AscDescError ::ReservedKeyword { name : text . to_string ( ) } )
} ) ? ;
2021-10-07 15:42:08 +02:00
if ! ( - 90. 0 ..= 90.0 ) . contains ( & lat ) {
return Err ( AscDescError ::BadLat ) ? ;
} else if ! ( - 180. 0 ..= 180.0 ) . contains ( & lng ) {
return Err ( AscDescError ::BadLng ) ? ;
}
Ok ( Member ::Geo ( [ lat , lng ] ) )
2021-09-22 15:18:39 +02:00
}
None = > {
if is_reserved_keyword ( text ) | | text . starts_with ( " _geoRadius( " ) {
return Err ( AscDescError ::ReservedKeyword { name : text . to_string ( ) } ) ? ;
}
Ok ( Member ::Field ( text . to_string ( ) ) )
}
}
}
}
impl fmt ::Display for Member {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
match self {
Member ::Field ( name ) = > f . write_str ( name ) ,
Member ::Geo ( [ lat , lng ] ) = > write! ( f , " _geoPoint({}, {}) " , lat , lng ) ,
}
}
}
impl Member {
pub fn field ( & self ) -> Option < & str > {
match self {
Member ::Field ( field ) = > Some ( field ) ,
Member ::Geo ( _ ) = > None ,
}
}
pub fn geo_point ( & self ) -> Option < & [ f64 ; 2 ] > {
match self {
Member ::Geo ( point ) = > Some ( point ) ,
Member ::Field ( _ ) = > None ,
}
}
}
#[ derive(Debug, Serialize, Deserialize, Clone, PartialEq) ]
pub enum AscDesc {
Asc ( Member ) ,
Desc ( Member ) ,
}
impl AscDesc {
pub fn member ( & self ) -> & Member {
match self {
AscDesc ::Asc ( member ) = > member ,
AscDesc ::Desc ( member ) = > member ,
}
}
pub fn field ( & self ) -> Option < & str > {
self . member ( ) . field ( )
}
}
impl FromStr for AscDesc {
type Err = AscDescError ;
fn from_str ( text : & str ) -> Result < AscDesc , Self ::Err > {
match text . rsplit_once ( ':' ) {
Some ( ( left , " asc " ) ) = > Ok ( AscDesc ::Asc ( left . parse ( ) ? ) ) ,
Some ( ( left , " desc " ) ) = > Ok ( AscDesc ::Desc ( left . parse ( ) ? ) ) ,
_ = > Err ( AscDescError ::InvalidSyntax { name : text . to_string ( ) } ) ,
}
}
}
2021-09-27 19:07:22 +02:00
#[ derive(Debug) ]
pub enum SortError {
2021-10-07 15:42:08 +02:00
BadLat ,
BadLng ,
2021-09-28 14:32:24 +02:00
BadGeoPointUsage { name : String } ,
2021-09-27 19:07:22 +02:00
InvalidName { name : String } ,
ReservedName { name : String } ,
ReservedNameForSettings { name : String } ,
ReservedNameForFilter { name : String } ,
}
impl From < AscDescError > for SortError {
fn from ( error : AscDescError ) -> Self {
match error {
2021-10-07 15:42:08 +02:00
AscDescError ::BadLat = > SortError ::BadLat ,
AscDescError ::BadLng = > SortError ::BadLng ,
2021-09-27 19:07:22 +02:00
AscDescError ::InvalidSyntax { name } = > SortError ::InvalidName { name } ,
2021-09-28 14:32:24 +02:00
AscDescError ::ReservedKeyword { name } if name . starts_with ( " _geoPoint " ) = > {
SortError ::BadGeoPointUsage { name }
}
2021-09-27 19:07:22 +02:00
AscDescError ::ReservedKeyword { name } if & name = = " _geo " = > {
2021-09-30 12:50:40 +02:00
SortError ::ReservedNameForSettings { name }
2021-09-27 19:07:22 +02:00
}
AscDescError ::ReservedKeyword { name } if name . starts_with ( " _geoRadius " ) = > {
2021-09-30 12:50:40 +02:00
SortError ::ReservedNameForFilter { name : String ::from ( " _geoRadius " ) }
2021-09-27 19:07:22 +02:00
}
AscDescError ::ReservedKeyword { name } = > SortError ::ReservedName { name } ,
}
}
}
impl fmt ::Display for SortError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
2021-10-07 15:42:08 +02:00
Self ::BadLat = > {
write! ( f , " Latitude must be contained between -90 and 90 degrees. " , )
}
Self ::BadLng = > {
write! ( f , " Longitude must be contained between -180 and 180 degrees. " , )
}
2021-09-28 14:32:24 +02:00
Self ::BadGeoPointUsage { name } = > {
write! (
f ,
" invalid syntax for the `_geoPoint` parameter: `{}`. \
2021-09-30 12:50:40 +02:00
Usage : ` _geoPoint ( latitude , longitude ) :asc ` . " ,
2021-09-28 14:32:24 +02:00
name
)
}
2021-09-27 19:07:22 +02:00
Self ::InvalidName { name } = > {
2021-09-30 12:50:40 +02:00
write! ( f , " invalid syntax for the sort parameter `{}`. " , name )
2021-09-27 19:07:22 +02:00
}
Self ::ReservedName { name } = > {
write! (
f ,
2021-09-30 12:50:40 +02:00
" {} is a reserved keyword and thus can't be used as a sort expression. " ,
2021-09-27 19:07:22 +02:00
name
)
}
2021-09-30 12:50:40 +02:00
Self ::ReservedNameForSettings { name } | Self ::ReservedNameForFilter { name } = > {
2021-09-27 19:07:22 +02:00
write! (
f ,
2021-09-30 12:50:40 +02:00
" `{}` is a reserved keyword and thus can't be used as a sort expression. \
Use the ` _geoPoint ( latitude , longitude ) ` built - in rule to sort on ` _geo ` field coordinates . " ,
name ,
2021-09-27 19:07:22 +02:00
)
}
}
}
}
impl From < SortError > for Error {
fn from ( error : SortError ) -> Self {
Self ::UserError ( UserError ::SortError ( error ) )
}
}
2021-09-22 15:18:39 +02:00
#[ cfg(test) ]
mod tests {
use big_s ::S ;
use AscDesc ::* ;
use AscDescError ::* ;
use Member ::* ;
use super ::* ;
#[ test ]
fn parse_asc_desc ( ) {
let valid_req = [
( " truc:asc " , Asc ( Field ( S ( " truc " ) ) ) ) ,
( " bidule:desc " , Desc ( Field ( S ( " bidule " ) ) ) ) ,
( " a-b:desc " , Desc ( Field ( S ( " a-b " ) ) ) ) ,
( " a:b:desc " , Desc ( Field ( S ( " a:b " ) ) ) ) ,
( " a12:asc " , Asc ( Field ( S ( " a12 " ) ) ) ) ,
( " 42:asc " , Asc ( Field ( S ( " 42 " ) ) ) ) ,
( " _geoPoint(42, 59):asc " , Asc ( Geo ( [ 42. , 59. ] ) ) ) ,
( " _geoPoint(42.459, 59):desc " , Desc ( Geo ( [ 42.459 , 59. ] ) ) ) ,
( " _geoPoint(42, 59.895):desc " , Desc ( Geo ( [ 42. , 59.895 ] ) ) ) ,
( " _geoPoint(42, 59.895):desc " , Desc ( Geo ( [ 42. , 59.895 ] ) ) ) ,
2021-10-07 15:42:08 +02:00
( " _geoPoint(90.000000000, 180):desc " , Desc ( Geo ( [ 90. , 180. ] ) ) ) ,
( " _geoPoint(-90, -180.0000000000):asc " , Asc ( Geo ( [ - 90. , - 180. ] ) ) ) ,
2021-09-22 15:18:39 +02:00
( " _geoPoint(42.0002, 59.895):desc " , Desc ( Geo ( [ 42.0002 , 59.895 ] ) ) ) ,
( " _geoPoint(42., 59.):desc " , Desc ( Geo ( [ 42. , 59. ] ) ) ) ,
( " truc(12, 13):desc " , Desc ( Field ( S ( " truc(12, 13) " ) ) ) ) ,
] ;
for ( req , expected ) in valid_req {
let res = req . parse ::< AscDesc > ( ) ;
assert! (
res . is_ok ( ) ,
" Failed to parse `{}`, was expecting `{:?}` but instead got `{:?}` " ,
req ,
expected ,
res
) ;
assert_eq! ( res . unwrap ( ) , expected ) ;
}
let invalid_req = [
( " truc:machin " , InvalidSyntax { name : S ( " truc:machin " ) } ) ,
( " truc:deesc " , InvalidSyntax { name : S ( " truc:deesc " ) } ) ,
( " truc:asc:deesc " , InvalidSyntax { name : S ( " truc:asc:deesc " ) } ) ,
( " 42desc " , InvalidSyntax { name : S ( " 42desc " ) } ) ,
( " _geoPoint:asc " , ReservedKeyword { name : S ( " _geoPoint " ) } ) ,
( " _geoDistance:asc " , ReservedKeyword { name : S ( " _geoDistance " ) } ) ,
( " _geoPoint(42.12 , 59.598) " , InvalidSyntax { name : S ( " _geoPoint(42.12 , 59.598) " ) } ) ,
(
" _geoPoint(42.12 , 59.598):deesc " ,
InvalidSyntax { name : S ( " _geoPoint(42.12 , 59.598):deesc " ) } ,
) ,
(
" _geoPoint(42.12 , 59.598):machin " ,
InvalidSyntax { name : S ( " _geoPoint(42.12 , 59.598):machin " ) } ,
) ,
(
" _geoPoint(42.12 , 59.598):asc:aasc " ,
InvalidSyntax { name : S ( " _geoPoint(42.12 , 59.598):asc:aasc " ) } ,
) ,
(
" _geoPoint(42,12 , 59,598):desc " ,
ReservedKeyword { name : S ( " _geoPoint(42,12 , 59,598) " ) } ,
) ,
( " _geoPoint(35, 85, 75):asc " , ReservedKeyword { name : S ( " _geoPoint(35, 85, 75) " ) } ) ,
( " _geoPoint(18):asc " , ReservedKeyword { name : S ( " _geoPoint(18) " ) } ) ,
2021-10-07 15:42:08 +02:00
( " _geoPoint(200, 200):asc " , BadLat ) ,
( " _geoPoint(90.000001, 0):asc " , BadLat ) ,
( " _geoPoint(0, -180.000001):desc " , BadLng ) ,
( " _geoPoint(159.256, 130):asc " , BadLat ) ,
( " _geoPoint(12, -2021):desc " , BadLng ) ,
2021-09-22 15:18:39 +02:00
] ;
for ( req , expected_error ) in invalid_req {
let res = req . parse ::< AscDesc > ( ) ;
assert! (
res . is_err ( ) ,
" Should no be able to parse `{}`, was expecting an error but instead got: `{:?}` " ,
req ,
res ,
) ;
let res = res . unwrap_err ( ) ;
assert_eq! (
res . to_string ( ) ,
expected_error . to_string ( ) ,
" Bad error for input {}: got `{:?}` instead of `{:?}` " ,
req ,
res ,
expected_error
) ;
}
}
2021-09-30 12:50:40 +02:00
#[ test ]
fn sort_error_message ( ) {
let errors = [
(
AscDescError ::InvalidSyntax { name : S ( " truc:machin " ) } ,
S ( " invalid syntax for the sort parameter `truc:machin`. " ) ,
) ,
(
AscDescError ::InvalidSyntax { name : S ( " hello:world " ) } ,
S ( " invalid syntax for the sort parameter `hello:world`. " ) ,
) ,
(
AscDescError ::ReservedKeyword { name : S ( " _geo " ) } ,
S ( " `_geo` is a reserved keyword and thus can't be used as a sort expression. Use the `_geoPoint(latitude, longitude)` built-in rule to sort on `_geo` field coordinates. " ) ,
) ,
(
AscDescError ::ReservedKeyword { name : S ( " _geoDistance " ) } ,
S ( " _geoDistance is a reserved keyword and thus can't be used as a sort expression. " )
) ,
(
AscDescError ::ReservedKeyword { name : S ( " _geoRadius(12, 13) " ) } ,
S ( " `_geoRadius` is a reserved keyword and thus can't be used as a sort expression. Use the `_geoPoint(latitude, longitude)` built-in rule to sort on `_geo` field coordinates. " ) ,
) ,
2021-10-07 15:42:08 +02:00
(
AscDescError ::BadLat ,
S ( " Latitude must be contained between -90 and 90 degrees. " ) ,
) ,
(
AscDescError ::BadLng ,
S ( " Longitude must be contained between -180 and 180 degrees. " ) ,
) ,
2021-09-30 12:50:40 +02:00
] ;
for ( asc_desc_error , expected_message ) in errors {
let sort_error = SortError ::from ( asc_desc_error ) ;
assert_eq! (
sort_error . to_string ( ) ,
expected_message ,
" was expecting {} for the error {:?} but instead got {} " ,
expected_message ,
sort_error ,
sort_error . to_string ( )
) ;
}
}
2021-09-22 15:18:39 +02:00
}