1use actix_web::{body::BoxBody, HttpRequest, HttpResponse, Responder};
2use paginator_rs::{PaginationParams, PaginatorResponse, PaginatorResponseMeta, SortDirection};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Deserialize)]
6pub struct PaginationQuery {
7 #[serde(default = "default_page")]
8 pub page: u32,
9 #[serde(default = "default_per_page")]
10 pub per_page: u32,
11 pub sort_by: Option<String>,
12 pub sort_direction: Option<String>,
13}
14
15fn default_page() -> u32 {
16 1
17}
18
19fn default_per_page() -> u32 {
20 20
21}
22
23impl PaginationQuery {
24 pub fn into_params(self) -> PaginationParams {
25 let sort_direction = self
26 .sort_direction
27 .and_then(|s| match s.to_lowercase().as_str() {
28 "asc" => Some(SortDirection::Asc),
29 "desc" => Some(SortDirection::Desc),
30 _ => None,
31 });
32
33 PaginationParams {
34 page: self.page.max(1),
35 per_page: self.per_page.clamp(1, 100),
36 sort_by: self.sort_by,
37 sort_direction,
38 filters: Vec::new(),
39 search: None,
40 disable_total_count: false,
41 cursor: None,
42 }
43 }
44
45 pub fn as_params(&self) -> PaginationParams {
46 let sort_direction =
47 self.sort_direction
48 .as_ref()
49 .and_then(|s| match s.to_lowercase().as_str() {
50 "asc" => Some(SortDirection::Asc),
51 "desc" => Some(SortDirection::Desc),
52 _ => None,
53 });
54
55 PaginationParams {
56 page: self.page.max(1),
57 per_page: self.per_page.clamp(1, 100),
58 sort_by: self.sort_by.clone(),
59 sort_direction,
60 filters: Vec::new(),
61 search: None,
62 disable_total_count: false,
63 cursor: None,
64 }
65 }
66}
67
68#[derive(Debug)]
69pub struct PaginatedJson<T> {
70 response: PaginatorResponse<T>,
71}
72
73impl<T> PaginatedJson<T>
74where
75 T: Serialize,
76{
77 pub fn new(data: Vec<T>, params: &PaginationParams, total: u32) -> Self {
78 Self {
79 response: PaginatorResponse {
80 data,
81 meta: PaginatorResponseMeta::new(params.page, params.per_page, total),
82 },
83 }
84 }
85
86 pub fn from_response(response: PaginatorResponse<T>) -> Self {
87 Self { response }
88 }
89}
90
91impl<T> Responder for PaginatedJson<T>
92where
93 T: Serialize,
94{
95 type Body = BoxBody;
96
97 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
98 let mut response = HttpResponse::Ok();
99
100 if let Some(total) = self.response.meta.total {
101 response.insert_header(("X-Total-Count", total.to_string()));
102 }
103 if let Some(total_pages) = self.response.meta.total_pages {
104 response.insert_header(("X-Total-Pages", total_pages.to_string()));
105 }
106 response.insert_header(("X-Current-Page", self.response.meta.page.to_string()));
107 response.insert_header(("X-Per-Page", self.response.meta.per_page.to_string()));
108
109 response.json(&self.response)
110 }
111}
112
113pub fn create_paginated_response<T>(
114 data: Vec<T>,
115 params: &PaginationParams,
116 total: u32,
117) -> PaginatedJson<T>
118where
119 T: Serialize,
120{
121 PaginatedJson::new(data, params, total)
122}
123
124pub mod middleware {
125 use actix_web::{
126 dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
127 Error,
128 };
129 use futures_util::future::LocalBoxFuture;
130 use std::future::{ready, Ready};
131
132 pub struct PaginationMiddleware;
133
134 impl<S, B> Transform<S, ServiceRequest> for PaginationMiddleware
135 where
136 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
137 S::Future: 'static,
138 B: 'static,
139 {
140 type Response = ServiceResponse<B>;
141 type Error = Error;
142 type InitError = ();
143 type Transform = PaginationMiddlewareService<S>;
144 type Future = Ready<Result<Self::Transform, Self::InitError>>;
145
146 fn new_transform(&self, service: S) -> Self::Future {
147 ready(Ok(PaginationMiddlewareService { service }))
148 }
149 }
150
151 pub struct PaginationMiddlewareService<S> {
152 service: S,
153 }
154
155 impl<S, B> Service<ServiceRequest> for PaginationMiddlewareService<S>
156 where
157 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
158 S::Future: 'static,
159 B: 'static,
160 {
161 type Response = ServiceResponse<B>;
162 type Error = Error;
163 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
164
165 forward_ready!(service);
166
167 fn call(&self, req: ServiceRequest) -> Self::Future {
168 let fut = self.service.call(req);
169
170 Box::pin(async move {
171 let res = fut.await?;
172 Ok(res)
173 })
174 }
175 }
176}