Skip to content

Commit 119ba9c

Browse files
committed
PaginatedByTimeAndId
1 parent 0b616db commit 119ba9c

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

common/src/api/external/http_pagination.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ use crate::api::external::Name;
4545
use crate::api::external::NameOrId;
4646
use crate::api::external::ObjectIdentity;
4747
use crate::api::external::PaginationOrder;
48+
use chrono::DateTime;
49+
use chrono::Utc;
4850
use dropshot::HttpError;
4951
use dropshot::PaginationParams;
5052
use 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)]
425475
mod 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
}

common/tests/output/pagination-schema.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,53 @@ schema for pagination parameters: page selector, scan by name or id
279279
}
280280
}
281281
}
282+
schema for pagination parameters: page selector, scan by time and id
283+
{
284+
"$schema": "http://json-schema.org/draft-07/schema#",
285+
"title": "PageSelector_for_ScanByTimeAndId_for_Null_and_Tuple_of_DateTime_and_Uuid",
286+
"description": "Specifies which page of results we're on\n\nThis type is generic over the different scan modes that we support.",
287+
"type": "object",
288+
"required": [
289+
"last_seen"
290+
],
291+
"properties": {
292+
"last_seen": {
293+
"description": "value of the marker field last seen by the client",
294+
"type": "array",
295+
"items": [
296+
{
297+
"type": "string",
298+
"format": "date-time"
299+
},
300+
{
301+
"type": "string",
302+
"format": "uuid"
303+
}
304+
],
305+
"maxItems": 2,
306+
"minItems": 2
307+
},
308+
"sort_by": {
309+
"default": "ascending",
310+
"allOf": [
311+
{
312+
"$ref": "#/definitions/TimeAndIdSortMode"
313+
}
314+
]
315+
}
316+
},
317+
"definitions": {
318+
"TimeAndIdSortMode": {
319+
"description": "Supported set of sort modes for scanning by timestamp and ID\n\nCurrently, we only support scanning in ascending order.",
320+
"oneOf": [
321+
{
322+
"description": "sort in increasing order of timestamp and ID",
323+
"type": "string",
324+
"enum": [
325+
"ascending"
326+
]
327+
}
328+
]
329+
}
330+
}
331+
}

0 commit comments

Comments
 (0)