@@ -4,7 +4,8 @@ use crate::middleware::log_request::RequestLogExt;
44use crate :: middleware:: real_ip:: RealIp ;
55use crate :: models:: helpers:: with_count:: * ;
66use crate :: util:: errors:: { bad_request, AppResult } ;
7- use crate :: util:: { HeaderMapExt , RequestUtils } ;
7+ use crate :: util:: HeaderMapExt ;
8+ use std:: num:: NonZeroU32 ;
89
910use base64:: { engine:: general_purpose, Engine } ;
1011use diesel:: pg:: Pg ;
@@ -77,28 +78,30 @@ impl PaginationOptionsBuilder {
7778 }
7879
7980 pub ( crate ) fn gather ( self , parts : & Parts ) -> AppResult < PaginationOptions > {
80- let params = parts. query ( ) ;
81- let page_param = params. get ( "page" ) ;
82- let seek_param = params. get ( "seek" ) ;
81+ use axum:: extract:: Query ;
8382
84- if seek_param. is_some ( ) && page_param. is_some ( ) {
83+ #[ derive( Debug , Deserialize ) ]
84+ struct QueryParams {
85+ page : Option < NonZeroU32 > ,
86+ per_page : Option < NonZeroU32 > ,
87+ seek : Option < String > ,
88+ }
89+
90+ let Query ( params) = Query :: < QueryParams > :: try_from_uri ( & parts. uri )
91+ . map_err ( |err| bad_request ( err. body_text ( ) ) ) ?;
92+
93+ if params. seek . is_some ( ) && params. page . is_some ( ) {
8594 return Err ( bad_request (
8695 "providing both ?page= and ?seek= is unsupported" ,
8796 ) ) ;
8897 }
8998
90- let page = if let Some ( s) = page_param {
99+ let page = if let Some ( s) = params . page {
91100 if !self . enable_pages {
92101 return Err ( bad_request ( "?page= is not supported for this request" ) ) ;
93102 }
94103
95- let numeric_page = s. parse ( ) . map_err ( bad_request) ?;
96- if numeric_page < 1 {
97- return Err ( bad_request ( format_args ! (
98- "page indexing starts from 1, page {numeric_page} is invalid" ,
99- ) ) ) ;
100- }
101-
104+ let numeric_page = s. get ( ) ;
102105 if numeric_page > MAX_PAGE_BEFORE_SUSPECTED_BOT {
103106 parts. request_log ( ) . add ( "bot" , "suspected" ) ;
104107 }
@@ -119,28 +122,22 @@ impl PaginationOptionsBuilder {
119122 }
120123
121124 Page :: Numeric ( numeric_page)
122- } else if let Some ( s) = seek_param {
125+ } else if let Some ( s) = params . seek {
123126 if !self . enable_seek {
124127 return Err ( bad_request ( "?seek= is not supported for this request" ) ) ;
125128 }
126129
127- Page :: Seek ( RawSeekPayload ( s. clone ( ) ) )
130+ Page :: Seek ( RawSeekPayload ( s) )
128131 } else {
129132 Page :: Unspecified
130133 } ;
131134
132- let per_page = params
133- . get ( "per_page" )
134- . map ( |s| s. parse ( ) . map_err ( bad_request) )
135- . unwrap_or ( Ok ( DEFAULT_PER_PAGE ) ) ?;
135+ let per_page = params. per_page . map ( |p| p. get ( ) as i64 ) ;
136+ let per_page = per_page. unwrap_or ( DEFAULT_PER_PAGE ) ;
136137 if per_page > MAX_PER_PAGE {
137138 return Err ( bad_request ( format_args ! (
138139 "cannot request more than {MAX_PER_PAGE} items" ,
139140 ) ) ) ;
140- } else if per_page < 1 {
141- return Err ( bad_request ( format_args ! (
142- "cannot request less than 1 item, per_page {per_page} is invalid" ,
143- ) ) ) ;
144141 }
145142
146143 Ok ( PaginationOptions { page, per_page } )
@@ -529,6 +526,7 @@ pub(crate) use seek;
529526mod tests {
530527 use super :: * ;
531528 use http:: { Method , Request , StatusCode } ;
529+ use insta:: assert_snapshot;
532530
533531 #[ test]
534532 fn no_pagination_param ( ) {
@@ -539,13 +537,12 @@ mod tests {
539537
540538 #[ test]
541539 fn page_param_parsing ( ) {
542- let assert_error =
543- |query, msg| assert_pagination_error ( PaginationOptions :: builder ( ) , query, msg) ;
540+ let error = |query| pagination_error ( PaginationOptions :: builder ( ) , query) ;
544541
545- assert_error ( "page=" , " cannot parse integer from empty string") ;
546- assert_error ( "page=not_a_number" , " invalid digit found in string") ;
547- assert_error ( "page=1.0" , " invalid digit found in string") ;
548- assert_error ( "page=0" , "page indexing starts from 1, page 0 is invalid ") ;
542+ assert_snapshot ! ( error ( "page=" ) , @ "Failed to deserialize query string: cannot parse integer from empty string") ;
543+ assert_snapshot ! ( error ( "page=not_a_number" ) , @ "Failed to deserialize query string: invalid digit found in string") ;
544+ assert_snapshot ! ( error ( "page=1.0" ) , @ "Failed to deserialize query string: invalid digit found in string") ;
545+ assert_snapshot ! ( error ( "page=0" ) , @ "Failed to deserialize query string: invalid value: integer `0`, expected a nonzero u32 ") ;
549546
550547 let pagination = PaginationOptions :: builder ( )
551548 . gather ( & mock ( "page=5" ) )
@@ -555,17 +552,13 @@ mod tests {
555552
556553 #[ test]
557554 fn per_page_param_parsing ( ) {
558- let assert_error =
559- |query, msg| assert_pagination_error ( PaginationOptions :: builder ( ) , query, msg) ;
560-
561- assert_error ( "per_page=" , "cannot parse integer from empty string" ) ;
562- assert_error ( "per_page=not_a_number" , "invalid digit found in string" ) ;
563- assert_error ( "per_page=1.0" , "invalid digit found in string" ) ;
564- assert_error ( "per_page=101" , "cannot request more than 100 items" ) ;
565- assert_error (
566- "per_page=0" ,
567- "cannot request less than 1 item, per_page 0 is invalid" ,
568- ) ;
555+ let error = |query| pagination_error ( PaginationOptions :: builder ( ) , query) ;
556+
557+ assert_snapshot ! ( error( "per_page=" ) , @"Failed to deserialize query string: cannot parse integer from empty string" ) ;
558+ assert_snapshot ! ( error( "per_page=not_a_number" ) , @"Failed to deserialize query string: invalid digit found in string" ) ;
559+ assert_snapshot ! ( error( "per_page=1.0" ) , @"Failed to deserialize query string: invalid digit found in string" ) ;
560+ assert_snapshot ! ( error( "per_page=101" ) , @"cannot request more than 100 items" ) ;
561+ assert_snapshot ! ( error( "per_page=0" ) , @"Failed to deserialize query string: invalid value: integer `0`, expected a nonzero u32" ) ;
569562
570563 let pagination = PaginationOptions :: builder ( )
571564 . gather ( & mock ( "per_page=5" ) )
@@ -575,11 +568,8 @@ mod tests {
575568
576569 #[ test]
577570 fn seek_param_parsing ( ) {
578- assert_pagination_error (
579- PaginationOptions :: builder ( ) ,
580- "seek=OTg" ,
581- "?seek= is not supported for this request" ,
582- ) ;
571+ let error = pagination_error ( PaginationOptions :: builder ( ) , "seek=OTg" ) ;
572+ assert_snapshot ! ( error, @"?seek= is not supported for this request" ) ;
583573
584574 let pagination = PaginationOptions :: builder ( )
585575 . enable_seek ( true )
@@ -598,25 +588,20 @@ mod tests {
598588
599589 #[ test]
600590 fn both_page_and_seek ( ) {
601- assert_pagination_error (
602- PaginationOptions :: builder ( ) ,
603- "page=1&seek=OTg" ,
604- "providing both ?page= and ?seek= is unsupported" ,
605- ) ;
606- assert_pagination_error (
591+ let error = pagination_error ( PaginationOptions :: builder ( ) , "page=1&seek=OTg" ) ;
592+ assert_snapshot ! ( error, @"providing both ?page= and ?seek= is unsupported" ) ;
593+
594+ let error = pagination_error (
607595 PaginationOptions :: builder ( ) . enable_seek ( true ) ,
608596 "page=1&seek=OTg" ,
609- "providing both ?page= and ?seek= is unsupported" ,
610597 ) ;
598+ assert_snapshot ! ( error, @"providing both ?page= and ?seek= is unsupported" ) ;
611599 }
612600
613601 #[ test]
614602 fn disabled_pages ( ) {
615- assert_pagination_error (
616- PaginationOptions :: builder ( ) . enable_pages ( false ) ,
617- "page=1" ,
618- "?page= is not supported for this request" ,
619- ) ;
603+ let error = pagination_error ( PaginationOptions :: builder ( ) . enable_pages ( false ) , "page=1" ) ;
604+ assert_snapshot ! ( error, @"?page= is not supported for this request" ) ;
620605 }
621606
622607 #[ test]
@@ -749,11 +734,12 @@ mod tests {
749734 . 0
750735 }
751736
752- fn assert_pagination_error ( options : PaginationOptionsBuilder , query : & str , message : & str ) {
737+ fn pagination_error ( options : PaginationOptionsBuilder , query : & str ) -> String {
753738 let error = options. gather ( & mock ( query) ) . unwrap_err ( ) ;
754- assert_eq ! ( error. to_string( ) , message) ;
755739
756740 let response = error. response ( ) ;
757741 assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
742+
743+ error. to_string ( )
758744 }
759745}
0 commit comments