@@ -4,7 +4,8 @@ use crate::middleware::log_request::RequestLogExt;
4
4
use crate :: middleware:: real_ip:: RealIp ;
5
5
use crate :: models:: helpers:: with_count:: * ;
6
6
use crate :: util:: errors:: { bad_request, AppResult } ;
7
- use crate :: util:: { HeaderMapExt , RequestUtils } ;
7
+ use crate :: util:: HeaderMapExt ;
8
+ use std:: num:: NonZeroU32 ;
8
9
9
10
use base64:: { engine:: general_purpose, Engine } ;
10
11
use diesel:: pg:: Pg ;
@@ -77,28 +78,30 @@ impl PaginationOptionsBuilder {
77
78
}
78
79
79
80
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 ;
83
82
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 ( ) {
85
94
return Err ( bad_request (
86
95
"providing both ?page= and ?seek= is unsupported" ,
87
96
) ) ;
88
97
}
89
98
90
- let page = if let Some ( s) = page_param {
99
+ let page = if let Some ( s) = params . page {
91
100
if !self . enable_pages {
92
101
return Err ( bad_request ( "?page= is not supported for this request" ) ) ;
93
102
}
94
103
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 ( ) ;
102
105
if numeric_page > MAX_PAGE_BEFORE_SUSPECTED_BOT {
103
106
parts. request_log ( ) . add ( "bot" , "suspected" ) ;
104
107
}
@@ -119,28 +122,22 @@ impl PaginationOptionsBuilder {
119
122
}
120
123
121
124
Page :: Numeric ( numeric_page)
122
- } else if let Some ( s) = seek_param {
125
+ } else if let Some ( s) = params . seek {
123
126
if !self . enable_seek {
124
127
return Err ( bad_request ( "?seek= is not supported for this request" ) ) ;
125
128
}
126
129
127
- Page :: Seek ( RawSeekPayload ( s. clone ( ) ) )
130
+ Page :: Seek ( RawSeekPayload ( s) )
128
131
} else {
129
132
Page :: Unspecified
130
133
} ;
131
134
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 ) ;
136
137
if per_page > MAX_PER_PAGE {
137
138
return Err ( bad_request ( format_args ! (
138
139
"cannot request more than {MAX_PER_PAGE} items" ,
139
140
) ) ) ;
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
- ) ) ) ;
144
141
}
145
142
146
143
Ok ( PaginationOptions { page, per_page } )
@@ -529,6 +526,7 @@ pub(crate) use seek;
529
526
mod tests {
530
527
use super :: * ;
531
528
use http:: { Method , Request , StatusCode } ;
529
+ use insta:: assert_snapshot;
532
530
533
531
#[ test]
534
532
fn no_pagination_param ( ) {
@@ -539,13 +537,12 @@ mod tests {
539
537
540
538
#[ test]
541
539
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) ;
544
541
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 ") ;
549
546
550
547
let pagination = PaginationOptions :: builder ( )
551
548
. gather ( & mock ( "page=5" ) )
@@ -555,17 +552,13 @@ mod tests {
555
552
556
553
#[ test]
557
554
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" ) ;
569
562
570
563
let pagination = PaginationOptions :: builder ( )
571
564
. gather ( & mock ( "per_page=5" ) )
@@ -575,11 +568,8 @@ mod tests {
575
568
576
569
#[ test]
577
570
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" ) ;
583
573
584
574
let pagination = PaginationOptions :: builder ( )
585
575
. enable_seek ( true )
@@ -598,25 +588,20 @@ mod tests {
598
588
599
589
#[ test]
600
590
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 (
607
595
PaginationOptions :: builder ( ) . enable_seek ( true ) ,
608
596
"page=1&seek=OTg" ,
609
- "providing both ?page= and ?seek= is unsupported" ,
610
597
) ;
598
+ assert_snapshot ! ( error, @"providing both ?page= and ?seek= is unsupported" ) ;
611
599
}
612
600
613
601
#[ test]
614
602
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" ) ;
620
605
}
621
606
622
607
#[ test]
@@ -749,11 +734,12 @@ mod tests {
749
734
. 0
750
735
}
751
736
752
- fn assert_pagination_error ( options : PaginationOptionsBuilder , query : & str , message : & str ) {
737
+ fn pagination_error ( options : PaginationOptionsBuilder , query : & str ) -> String {
753
738
let error = options. gather ( & mock ( query) ) . unwrap_err ( ) ;
754
- assert_eq ! ( error. to_string( ) , message) ;
755
739
756
740
let response = error. response ( ) ;
757
741
assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
742
+
743
+ error. to_string ( )
758
744
}
759
745
}
0 commit comments