@@ -45,6 +45,8 @@ use crate::api::external::Name;
4545use crate :: api:: external:: NameOrId ;
4646use crate :: api:: external:: ObjectIdentity ;
4747use crate :: api:: external:: PaginationOrder ;
48+ use chrono:: DateTime ;
49+ use chrono:: Utc ;
4850use dropshot:: HttpError ;
4951use dropshot:: PaginationParams ;
5052use dropshot:: RequestContext ;
@@ -421,6 +423,54 @@ impl<T: Clone + Debug + DeserializeOwned + JsonSchema + PartialEq + Serialize>
421423 }
422424}
423425
426+ /// Query parameters for pagination by timestamp and ID
427+ pub type PaginatedByTimeAndId < Selector = ( ) > = PaginationParams <
428+ ScanByTimeAndId < Selector > ,
429+ PageSelectorByTimeAndId < Selector > ,
430+ > ;
431+ /// Page selector for pagination by timestamp and ID
432+ pub type PageSelectorByTimeAndId < Selector = ( ) > =
433+ PageSelector < ScanByTimeAndId < Selector > , ( DateTime < Utc > , Uuid ) > ;
434+
435+ /// Scan parameters for resources that support scanning by (timestamp, id)
436+ #[ derive( Clone , Debug , Deserialize , JsonSchema , PartialEq , Serialize ) ]
437+ pub struct ScanByTimeAndId < Selector = ( ) > {
438+ #[ serde( default = "default_ts_id_sort_mode" ) ]
439+ sort_by : TimeAndIdSortMode ,
440+
441+ #[ serde( flatten) ]
442+ pub selector : Selector ,
443+ }
444+
445+ /// Supported set of sort modes for scanning by timestamp and ID
446+ ///
447+ /// Currently, we only support scanning in ascending order.
448+ #[ derive( Copy , Clone , Debug , Deserialize , JsonSchema , PartialEq , Serialize ) ]
449+ #[ serde( rename_all = "snake_case" ) ]
450+ pub enum TimeAndIdSortMode {
451+ /// sort in increasing order of timestamp and ID
452+ Ascending ,
453+ }
454+
455+ fn default_ts_id_sort_mode ( ) -> TimeAndIdSortMode {
456+ TimeAndIdSortMode :: Ascending
457+ }
458+
459+ impl < T : Clone + Debug + DeserializeOwned + JsonSchema + PartialEq + Serialize >
460+ ScanParams for ScanByTimeAndId < T >
461+ {
462+ type MarkerValue = ( DateTime < Utc > , Uuid ) ;
463+ fn direction ( & self ) -> PaginationOrder {
464+ PaginationOrder :: Ascending
465+ }
466+ fn from_query ( p : & PaginatedByTimeAndId < T > ) -> Result < & Self , HttpError > {
467+ Ok ( match p. page {
468+ WhichPage :: First ( ref scan_params) => scan_params,
469+ WhichPage :: Next ( PageSelector { ref scan, .. } ) => scan,
470+ } )
471+ }
472+ }
473+
424474#[ cfg( test) ]
425475mod test {
426476 use super :: IdSortMode ;
@@ -432,14 +482,18 @@ mod test {
432482 use super :: PageSelectorById ;
433483 use super :: PageSelectorByName ;
434484 use super :: PageSelectorByNameOrId ;
485+ use super :: PageSelectorByTimeAndId ;
435486 use super :: PaginatedBy ;
436487 use super :: PaginatedById ;
437488 use super :: PaginatedByName ;
438489 use super :: PaginatedByNameOrId ;
490+ use super :: PaginatedByTimeAndId ;
439491 use super :: ScanById ;
440492 use super :: ScanByName ;
441493 use super :: ScanByNameOrId ;
494+ use super :: ScanByTimeAndId ;
442495 use super :: ScanParams ;
496+ use super :: TimeAndIdSortMode ;
443497 use super :: data_page_params_with_limit;
444498 use super :: marker_for_id;
445499 use super :: marker_for_name;
@@ -448,6 +502,7 @@ mod test {
448502 use crate :: api:: external:: IdentityMetadata ;
449503 use crate :: api:: external:: ObjectIdentity ;
450504 use crate :: api:: external:: http_pagination:: name_or_id_pagination;
505+ use chrono:: DateTime ;
451506 use chrono:: Utc ;
452507 use dropshot:: PaginationOrder ;
453508 use dropshot:: PaginationParams ;
@@ -486,6 +541,10 @@ mod test {
486541 "page selector, scan by name or id" ,
487542 schema_for!( PageSelectorByNameOrId ) ,
488543 ) ,
544+ (
545+ "page selector, scan by time and id" ,
546+ schema_for!( PageSelectorByTimeAndId ) ,
547+ ) ,
489548 ] ;
490549
491550 let mut found_output = String :: new ( ) ;
@@ -834,6 +893,7 @@ mod test {
834893 let thing0_marker = NameOrId :: Id ( list[ 0 ] . identity . id ) ;
835894 let thinglast_id = list[ list. len ( ) - 1 ] . identity . id ;
836895 let thinglast_marker = NameOrId :: Id ( list[ list. len ( ) - 1 ] . identity . id ) ;
896+
837897 let ( p0, p1) = test_scan_param_common (
838898 & list,
839899 & scan,
@@ -871,4 +931,61 @@ mod test {
871931 assert_eq ! ( data_page. direction, PaginationOrder :: Ascending ) ;
872932 assert_eq ! ( data_page. limit, limit) ;
873933 }
934+
935+ #[ test]
936+ fn test_scan_by_time_and_id ( ) {
937+ let scan = ScanByTimeAndId {
938+ sort_by : TimeAndIdSortMode :: Ascending ,
939+ selector : ( ) ,
940+ } ;
941+
942+ let list = list_of_things ( ) ;
943+ let item0_time = list[ 0 ] . identity . time_created ;
944+ let item0_id = list[ 0 ] . identity . id ;
945+ let item0_marker = ( item0_time, item0_id) ;
946+
947+ let last_idx = list. len ( ) - 1 ;
948+ let item_last_time = list[ last_idx] . identity . time_created ;
949+ let item_last_id = list[ last_idx] . identity . id ;
950+ let item_last_marker = ( item_last_time, item_last_id) ;
951+
952+ let marker_fn =
953+ |_: & ScanByTimeAndId , item : & MyThing | -> ( DateTime < Utc > , Uuid ) {
954+ ( item. identity . time_created , item. identity . id )
955+ } ;
956+ let ( p0, p1) = test_scan_param_common (
957+ & list,
958+ & scan,
959+ "sort_by=ascending" ,
960+ & item0_marker,
961+ & item_last_marker,
962+ & scan,
963+ & marker_fn,
964+ ) ;
965+
966+ assert_eq ! ( scan. direction( ) , PaginationOrder :: Ascending ) ;
967+
968+ // Verify data pages based on the query params.
969+ let limit = NonZeroU32 :: new ( 123 ) . unwrap ( ) ;
970+ let data_page = data_page_params_with_limit ( limit, & p0) . unwrap ( ) ;
971+ assert_eq ! ( data_page. marker, None ) ;
972+ assert_eq ! ( data_page. direction, PaginationOrder :: Ascending ) ;
973+ assert_eq ! ( data_page. limit, limit) ;
974+
975+ let data_page = data_page_params_with_limit ( limit, & p1) . unwrap ( ) ;
976+ assert_eq ! ( data_page. marker, Some ( & item_last_marker) ) ;
977+ assert_eq ! ( data_page. direction, PaginationOrder :: Ascending ) ;
978+ assert_eq ! ( data_page. limit, limit) ;
979+
980+ // Test error case
981+ let error = serde_urlencoded:: from_str :: < PaginatedByTimeAndId > (
982+ "sort_by=descending" ,
983+ )
984+ . unwrap_err ( ) ;
985+
986+ assert_eq ! (
987+ error. to_string( ) ,
988+ "unknown variant `descending`, expected `ascending`"
989+ ) ;
990+ }
874991}
0 commit comments