Skip to content

Commit d6afff2

Browse files
committed
controllers/helpers/pagination: Add seek! helper macro
1 parent 4289fa1 commit d6afff2

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ object_store = { version = "=0.9.0", features = ["aws"] }
9797
once_cell = "=1.19.0"
9898
p256 = "=0.13.2"
9999
parking_lot = "=0.12.1"
100+
paste = "=1.0.14"
100101
prometheus = { version = "=0.13.3", default-features = false }
101102
rand = "=0.8.5"
102103
reqwest = { version = "=0.11.23", features = ["gzip", "json"] }

src/controllers/helpers/pagination.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,73 @@ impl<T, C> PaginatedQueryWithCountSubq<T, C> {
395395
}
396396
}
397397

398+
macro_rules! seek {
399+
(
400+
$vis:vis enum $name:ident {
401+
$(
402+
$variant:ident($($(#[$field_meta:meta])? $ty:ty),*)
403+
)*
404+
}
405+
) => {
406+
paste::item! {
407+
$(
408+
#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
409+
$vis struct $variant($($(#[$field_meta])? pub(super) $ty),*);
410+
)*
411+
412+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
413+
#[serde(untagged)]
414+
$vis enum [<$name Payload>] {
415+
$(
416+
$variant($variant),
417+
)*
418+
}
419+
420+
#[derive(Debug, PartialEq)]
421+
$vis enum $name {
422+
$(
423+
$variant,
424+
)*
425+
}
426+
427+
$(
428+
impl From<$variant> for [<$name Payload>] {
429+
fn from(value: $variant) -> Self {
430+
[<$name Payload>]::$variant(value)
431+
}
432+
}
433+
)*
434+
impl From<[<$name Payload>]> for $name {
435+
fn from(value: [<$name Payload>]) -> Self {
436+
match value {
437+
$(
438+
[<$name Payload>]::$variant(_) => $name::$variant,
439+
)*
440+
}
441+
}
442+
}
443+
444+
use crate::util::errors::AppResult;
445+
use crate::controllers::helpers::pagination::Page;
446+
impl $name {
447+
pub fn after(&self, page: &Page) -> AppResult<Option<[<$name Payload>]>> {
448+
let Page::Seek(ref encoded) = *page else {
449+
return Ok(None);
450+
};
451+
452+
Ok(Some(match self {
453+
$(
454+
$name::$variant => encoded.decode::<$variant>()?.into(),
455+
)*
456+
}))
457+
}
458+
}
459+
}
460+
};
461+
}
462+
463+
pub(crate) use seek;
464+
398465
#[cfg(test)]
399466
mod tests {
400467
use super::*;
@@ -499,6 +566,66 @@ mod tests {
499566
);
500567
}
501568

569+
mod seek {
570+
use chrono::naive::serde::ts_microseconds;
571+
seek! {
572+
pub(super) enum Seek {
573+
Id(i32)
574+
New(#[serde(with="ts_microseconds")] chrono::NaiveDateTime, i32)
575+
RecentDownloads(Option<i64>, i32)
576+
}
577+
}
578+
}
579+
580+
#[test]
581+
fn test_seek_macro_encode_and_decode() {
582+
use chrono::{NaiveDate, NaiveDateTime};
583+
use seek::*;
584+
585+
let assert_decode_after = |seek: Seek, query: &str, expect| {
586+
let pagination = PaginationOptions::builder()
587+
.enable_seek(true)
588+
.gather(&mock(query))
589+
.unwrap();
590+
let decoded = seek.after(&pagination.page).unwrap();
591+
assert_eq!(decoded, expect);
592+
};
593+
594+
let seek = Seek::Id;
595+
let payload = SeekPayload::Id(Id(1234));
596+
let query = format!("seek={}", encode_seek(&payload).unwrap());
597+
assert_decode_after(seek, &query, Some(payload));
598+
599+
let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8)
600+
.unwrap()
601+
.and_hms_opt(9, 10, 11)
602+
.unwrap();
603+
let seek = Seek::New;
604+
let payload = SeekPayload::New(New(dt, 1234));
605+
let query = format!("seek={}", encode_seek(&payload).unwrap());
606+
assert_decode_after(seek, &query, Some(payload));
607+
608+
let seek = Seek::RecentDownloads;
609+
let payload = SeekPayload::RecentDownloads(RecentDownloads(Some(5678), 1234));
610+
let query = format!("seek={}", encode_seek(&payload).unwrap());
611+
assert_decode_after(seek, &query, Some(payload));
612+
613+
let seek = Seek::Id;
614+
assert_decode_after(seek, "", None);
615+
616+
let seek = Seek::Id;
617+
let payload = SeekPayload::RecentDownloads(RecentDownloads(Some(5678), 1234));
618+
let query = format!("seek={}", encode_seek(payload).unwrap());
619+
let pagination = PaginationOptions::builder()
620+
.enable_seek(true)
621+
.gather(&mock(&query))
622+
.unwrap();
623+
let error = seek.after(&pagination.page).unwrap_err();
624+
assert_eq!(error.to_string(), "invalid seek parameter");
625+
let response = error.response();
626+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
627+
}
628+
502629
fn mock(query: &str) -> Request<()> {
503630
Request::builder()
504631
.method(Method::GET)

0 commit comments

Comments
 (0)