@@ -397,6 +397,73 @@ impl<T, C> PaginatedQueryWithCountSubq<T, C> {
397
397
}
398
398
}
399
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
+
400
467
#[ cfg( test) ]
401
468
mod tests {
402
469
use super :: * ;
@@ -501,6 +568,85 @@ mod tests {
501
568
) ;
502
569
}
503
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
+
504
650
fn mock ( query : & str ) -> Request < ( ) > {
505
651
Request :: builder ( )
506
652
. method ( Method :: GET )
0 commit comments