@@ -198,6 +198,31 @@ impl<T> Paginated<T> {
198
198
Some ( opts)
199
199
}
200
200
201
+ pub ( crate ) fn next_seek_params < S , F > ( & self , f : F ) -> AppResult < Option < IndexMap < String , String > > >
202
+ where
203
+ F : Fn ( & T ) -> S ,
204
+ S : Serialize ,
205
+ {
206
+ if self . is_explicit_page ( ) || self . records_and_total . len ( ) < self . options . per_page as usize
207
+ {
208
+ return Ok ( None ) ;
209
+ }
210
+
211
+ let mut opts = IndexMap :: new ( ) ;
212
+ match self . options . page {
213
+ Page :: Unspecified | Page :: Seek ( _) => {
214
+ let seek = f ( & self . records_and_total . last ( ) . unwrap ( ) . record ) ;
215
+ opts. insert ( "seek" . into ( ) , encode_seek ( seek) ?) ;
216
+ }
217
+ Page :: Numeric ( _) => unreachable ! ( ) ,
218
+ } ;
219
+ Ok ( Some ( opts) )
220
+ }
221
+
222
+ fn is_explicit_page ( & self ) -> bool {
223
+ matches ! ( & self . options. page, Page :: Numeric ( _) )
224
+ }
225
+
201
226
pub ( crate ) fn iter ( & self ) -> impl Iterator < Item = & T > {
202
227
self . records_and_total . iter ( ) . map ( |row| & row. record )
203
228
}
@@ -372,6 +397,73 @@ impl<T, C> PaginatedQueryWithCountSubq<T, C> {
372
397
}
373
398
}
374
399
400
+ macro_rules! seek {
401
+ (
402
+ $vis: vis enum $name: ident {
403
+ $(
404
+ $variant: ident( $( $( #[ $field_meta: meta] ) ? $ty: ty) ,* )
405
+ ) *
406
+ }
407
+ ) => {
408
+ paste:: item! {
409
+ $(
410
+ #[ derive( Debug , Default , Deserialize , Serialize , PartialEq ) ]
411
+ $vis struct $variant( $( $( #[ $field_meta] ) ? pub ( super ) $ty) ,* ) ;
412
+ ) *
413
+
414
+ #[ derive( Debug , Deserialize , Serialize , PartialEq ) ]
415
+ #[ serde( untagged) ]
416
+ $vis enum [ <$name Payload >] {
417
+ $(
418
+ $variant( $variant) ,
419
+ ) *
420
+ }
421
+
422
+ #[ derive( Debug , PartialEq ) ]
423
+ $vis enum $name {
424
+ $(
425
+ $variant,
426
+ ) *
427
+ }
428
+
429
+ $(
430
+ impl From <$variant> for [ <$name Payload >] {
431
+ fn from( value: $variant) -> Self {
432
+ [ <$name Payload >] :: $variant( value)
433
+ }
434
+ }
435
+ ) *
436
+ impl From <[ <$name Payload >] > for $name {
437
+ fn from( value: [ <$name Payload >] ) -> Self {
438
+ match value {
439
+ $(
440
+ [ <$name Payload >] :: $variant( _) => $name:: $variant,
441
+ ) *
442
+ }
443
+ }
444
+ }
445
+
446
+ use crate :: util:: errors:: AppResult ;
447
+ use crate :: controllers:: helpers:: pagination:: Page ;
448
+ impl $name {
449
+ pub fn after( & self , page: & Page ) -> AppResult <Option <[ <$name Payload >] >> {
450
+ let Page :: Seek ( ref encoded) = * page else {
451
+ return Ok ( None ) ;
452
+ } ;
453
+
454
+ Ok ( Some ( match self {
455
+ $(
456
+ $name:: $variant => encoded. decode:: <$variant>( ) ?. into( ) ,
457
+ ) *
458
+ } ) )
459
+ }
460
+ }
461
+ }
462
+ } ;
463
+ }
464
+
465
+ pub ( crate ) use seek;
466
+
375
467
#[ cfg( test) ]
376
468
mod tests {
377
469
use super :: * ;
@@ -476,6 +568,85 @@ mod tests {
476
568
) ;
477
569
}
478
570
571
+ mod seek {
572
+ use chrono:: naive:: serde:: ts_microseconds;
573
+ seek ! {
574
+ pub ( super ) enum Seek {
575
+ Id ( i32 )
576
+ New ( #[ serde( with="ts_microseconds" ) ] chrono:: NaiveDateTime , i32 )
577
+ RecentDownloads ( Option <i64 >, i32 )
578
+ }
579
+ }
580
+ }
581
+
582
+ #[ test]
583
+ fn test_seek_macro_encode_and_decode ( ) {
584
+ use chrono:: { NaiveDate , NaiveDateTime } ;
585
+ use seek:: * ;
586
+
587
+ let assert_decode_after = |seek : Seek , query : & str , expect| {
588
+ let pagination = PaginationOptions :: builder ( )
589
+ . enable_seek ( true )
590
+ . gather ( & mock ( query) )
591
+ . unwrap ( ) ;
592
+ let decoded = seek. after ( & pagination. page ) . unwrap ( ) ;
593
+ assert_eq ! ( decoded, expect) ;
594
+ } ;
595
+
596
+ let seek = Seek :: Id ;
597
+ let payload = SeekPayload :: Id ( Id ( 1234 ) ) ;
598
+ let query = format ! ( "seek={}" , encode_seek( & payload) . unwrap( ) ) ;
599
+ assert_decode_after ( seek, & query, Some ( payload) ) ;
600
+
601
+ let dt: NaiveDateTime = NaiveDate :: from_ymd_opt ( 2016 , 7 , 8 )
602
+ . unwrap ( )
603
+ . and_hms_opt ( 9 , 10 , 11 )
604
+ . unwrap ( ) ;
605
+ let seek = Seek :: New ;
606
+ let payload = SeekPayload :: New ( New ( dt, 1234 ) ) ;
607
+ let query = format ! ( "seek={}" , encode_seek( & payload) . unwrap( ) ) ;
608
+ assert_decode_after ( seek, & query, Some ( payload) ) ;
609
+
610
+ let seek = Seek :: RecentDownloads ;
611
+ let payload = SeekPayload :: RecentDownloads ( RecentDownloads ( Some ( 5678 ) , 1234 ) ) ;
612
+ let query = format ! ( "seek={}" , encode_seek( & payload) . unwrap( ) ) ;
613
+ assert_decode_after ( seek, & query, Some ( payload) ) ;
614
+
615
+ let seek = Seek :: Id ;
616
+ assert_decode_after ( seek, "" , None ) ;
617
+
618
+ let seek = Seek :: Id ;
619
+ let payload = SeekPayload :: RecentDownloads ( RecentDownloads ( Some ( 5678 ) , 1234 ) ) ;
620
+ let query = format ! ( "seek={}" , encode_seek( payload) . unwrap( ) ) ;
621
+ let pagination = PaginationOptions :: builder ( )
622
+ . enable_seek ( true )
623
+ . gather ( & mock ( & query) )
624
+ . unwrap ( ) ;
625
+ let error = seek. after ( & pagination. page ) . unwrap_err ( ) ;
626
+ assert_eq ! ( error. to_string( ) , "invalid seek parameter" ) ;
627
+ let response = error. response ( ) ;
628
+ assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
629
+ }
630
+
631
+ #[ test]
632
+ fn test_seek_macro_conv ( ) {
633
+ use chrono:: { NaiveDate , NaiveDateTime } ;
634
+ use seek:: * ;
635
+
636
+ assert_eq ! ( Seek :: from( SeekPayload :: Id ( Id ( 1234 ) ) ) , Seek :: Id ) ;
637
+
638
+ let dt: NaiveDateTime = NaiveDate :: from_ymd_opt ( 2016 , 7 , 8 )
639
+ . unwrap ( )
640
+ . and_hms_opt ( 9 , 10 , 11 )
641
+ . unwrap ( ) ;
642
+ assert_eq ! ( Seek :: from( SeekPayload :: New ( New ( dt, 1234 ) ) ) , Seek :: New ) ;
643
+
644
+ assert_eq ! (
645
+ Seek :: from( SeekPayload :: RecentDownloads ( RecentDownloads ( None , 1234 ) ) ) ,
646
+ Seek :: RecentDownloads
647
+ ) ;
648
+ }
649
+
479
650
fn mock ( query : & str ) -> Request < ( ) > {
480
651
Request :: builder ( )
481
652
. method ( Method :: GET )
0 commit comments