Skip to content

Commit

Permalink
controllers/helpers/pagination: Add named fields struct support for `…
Browse files Browse the repository at this point in the history
…seek!`
  • Loading branch information
eth3lbert committed Mar 6, 2024
1 parent 74892ed commit 9f57dd4
Showing 1 changed file with 121 additions and 6 deletions.
127 changes: 121 additions & 6 deletions src/controllers/helpers/pagination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,20 +401,56 @@ impl<T, C> PaginatedQueryWithCountSubq<T, C> {
}
}

#[allow(unused_macro_rules)]
macro_rules! seek {
// Tuple struct
(@variant_struct $vis:vis $variant:ident($($(#[$field_meta:meta])? $ty:ty),*)) => {
#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
$vis struct $variant($($(#[$field_meta])? pub(super) $ty),*);
};
// Field struct
(@variant_struct $vis:vis $variant:ident {
$($(#[$field_meta:meta])? $field:ident: $ty:ty),*
}) => {
paste::item! {
#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(from = $variant "Helper")]
$vis struct $variant {
$($(#[$field_meta])? pub(super) $field: $ty),*
}

#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
struct [<$variant Helper>]($($(#[$field_meta])? pub(super) $ty),*);

impl From<[<$variant Helper>]> for $variant {
fn from(helper: [<$variant Helper>]) -> Self {
let [<$variant Helper>]($($field,)*) = helper;
Self { $($field,)* }
}
}

impl serde::Serialize for $variant {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let helper = [<$variant Helper>]($(self.$field,)*);
serde::Serialize::serialize(&helper, serializer)
}
}
}
};
(
$vis:vis enum $name:ident {
$(
$variant:ident($($(#[$field_meta:meta])? $ty:ty),*)
$variant:ident $fields:tt
)*
}
) => {
$(
seek!(@variant_struct $vis $variant $fields);
)*
paste::item! {
$(
#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
$vis struct $variant($($(#[$field_meta])? pub(super) $ty),*);
)*

#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
$vis enum [<$name Payload>] {
Expand Down Expand Up @@ -583,12 +619,16 @@ mod tests {
Id(i32)
New(#[serde(with="ts_microseconds")] chrono::NaiveDateTime, i32)
RecentDownloads(Option<i64>, i32)
NamedId{id: i32}
NamedNew{#[serde(with="ts_microseconds")] dt: chrono::NaiveDateTime, id: i32}
NamedRecentDownloads{ downloads: Option<i64>, id: i32 }
}
}
}

#[test]
fn test_seek_macro_encode_and_decode() {
use chrono::naive::serde::ts_microseconds;
use chrono::{NaiveDate, NaiveDateTime};
use seek::*;

Expand All @@ -601,6 +641,7 @@ mod tests {
assert_eq!(decoded, expect);
};

// Tuple struct
let seek = Seek::Id;
let payload = SeekPayload::Id(Id(1234));
let query = format!("seek={}", encode_seek(&payload).unwrap());
Expand Down Expand Up @@ -634,13 +675,62 @@ mod tests {
assert_eq!(error.to_string(), "invalid seek parameter");
let response = error.response();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);

// Field struct
let id = 1234;
let seek = Seek::NamedId;
let payload = SeekPayload::NamedId(NamedId { id });
let query = format!("seek={}", encode_seek(&payload).unwrap());
assert_decode_after(seek, &query, Some(payload));

let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8)
.unwrap()
.and_hms_opt(9, 10, 11)
.unwrap();
let seek = Seek::NamedNew;
let payload = SeekPayload::NamedNew(NamedNew { dt, id });
let query = format!("seek={}", encode_seek(&payload).unwrap());
assert_decode_after(seek, &query, Some(payload));

let downloads = Some(5678);
let seek = Seek::NamedRecentDownloads;
let payload = SeekPayload::NamedRecentDownloads(NamedRecentDownloads { downloads, id });
let query = format!("seek={}", encode_seek(&payload).unwrap());
assert_decode_after(seek, &query, Some(payload));

let seek = Seek::Id;
assert_decode_after(seek, "", None);

let seek = Seek::Id;
let payload = SeekPayload::NamedRecentDownloads(NamedRecentDownloads { downloads, id });
let query = format!("seek={}", encode_seek(payload).unwrap());
let pagination = PaginationOptions::builder()
.enable_seek(true)
.gather(&mock(&query))
.unwrap();
let error = seek.after(&pagination.page).unwrap_err();
assert_eq!(error.to_string(), "invalid seek parameter");
let response = error.response();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);

// Ensures it still encodes compactly with a field struct
#[derive(Debug, Default, Serialize, PartialEq)]
struct NewTuple(
#[serde(with = "ts_microseconds")] chrono::NaiveDateTime,
i32,
);
assert_eq!(
encode_seek(NewTuple(dt, id)).unwrap(),
encode_seek(SeekPayload::NamedNew(NamedNew { dt, id })).unwrap()
);
}

#[test]
fn test_seek_macro_conv() {
use chrono::{NaiveDate, NaiveDateTime};
use seek::*;

// Tuple struct
assert_eq!(Seek::from(SeekPayload::Id(Id(1234))), Seek::Id);

let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8)
Expand All @@ -653,6 +743,31 @@ mod tests {
Seek::from(SeekPayload::RecentDownloads(RecentDownloads(None, 1234))),
Seek::RecentDownloads
);

// Field struct
let id = 1234;
assert_eq!(
Seek::from(SeekPayload::NamedId(NamedId { id })),
Seek::NamedId
);

let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8)
.unwrap()
.and_hms_opt(9, 10, 11)
.unwrap();
assert_eq!(
Seek::from(SeekPayload::NamedNew(NamedNew { dt, id })),
Seek::NamedNew
);

let downloads = None;
assert_eq!(
Seek::from(SeekPayload::NamedRecentDownloads(NamedRecentDownloads {
downloads,
id
})),
Seek::NamedRecentDownloads
);
}

fn mock(query: &str) -> Request<()> {
Expand Down

0 comments on commit 9f57dd4

Please sign in to comment.