openzeppelin_relayer/models/
pagination.rs1use crate::models::TransactionStatus;
2use serde::de::IntoDeserializer;
3use serde::{Deserialize, Deserializer};
4use utoipa::ToSchema;
5
6#[derive(Debug, Deserialize, Clone, ToSchema)]
7pub struct PaginationQuery {
8 #[serde(default = "default_page")]
9 pub page: u32,
10 #[serde(default = "default_per_page")]
11 pub per_page: u32,
12}
13
14#[derive(Debug, Deserialize, Clone, ToSchema)]
16pub struct TransactionListQuery {
17 #[serde(default = "default_page")]
18 pub page: u32,
19 #[serde(default = "default_per_page")]
20 pub per_page: u32,
21 #[serde(default, deserialize_with = "deserialize_optional_status_ci")]
25 pub status: Option<TransactionStatus>,
26}
27
28fn deserialize_optional_status_ci<'de, D>(
33 deserializer: D,
34) -> Result<Option<TransactionStatus>, D::Error>
35where
36 D: Deserializer<'de>,
37{
38 let raw = String::deserialize(deserializer)?;
39 let lowered = raw.to_ascii_lowercase();
40 TransactionStatus::deserialize(lowered.as_str().into_deserializer())
41 .map(Some)
42 .map_err(|_: serde::de::value::Error| {
43 serde::de::Error::custom(format!("unknown transaction status: {raw}"))
44 })
45}
46
47impl From<TransactionListQuery> for PaginationQuery {
48 fn from(q: TransactionListQuery) -> Self {
49 PaginationQuery {
50 page: q.page,
51 per_page: q.per_page,
52 }
53 }
54}
55
56fn default_page() -> u32 {
57 1
58}
59fn default_per_page() -> u32 {
60 10
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 fn parse(qs: &str) -> Result<TransactionListQuery, actix_web::error::QueryPayloadError> {
70 actix_web::web::Query::<TransactionListQuery>::from_query(qs).map(|q| q.into_inner())
71 }
72
73 #[test]
74 fn status_is_parsed_case_insensitively() {
75 for qs in ["status=submitted", "status=Submitted", "status=SUBMITTED"] {
76 let q = parse(qs).unwrap_or_else(|e| panic!("{qs} should parse: {e}"));
77 assert_eq!(q.status, Some(TransactionStatus::Submitted), "for {qs}");
78 }
79 }
80
81 #[test]
82 fn status_absent_is_none_with_defaults() {
83 let q = parse("").unwrap();
84 assert_eq!(q.status, None);
85 assert_eq!(q.page, 1);
86 assert_eq!(q.per_page, 10);
87 }
88
89 #[test]
90 fn other_statuses_and_pagination_round_trip() {
91 let q = parse("page=2&per_page=5&status=CANCELED").unwrap();
92 assert_eq!(q.status, Some(TransactionStatus::Canceled));
93 assert_eq!(q.page, 2);
94 assert_eq!(q.per_page, 5);
95 }
96
97 #[test]
98 fn invalid_status_is_rejected() {
99 assert!(parse("status=bogus").is_err());
100 }
101}