Skip to main content

paginator_actix/
lib.rs

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}