mas_handlers/admin/
params.rs1#![allow(clippy::str_to_string)]
9
10use std::num::NonZeroUsize;
11
12use aide::OperationIo;
13use axum::{
14 Json,
15 extract::{
16 FromRequestParts, Path, Query,
17 rejection::{PathRejection, QueryRejection},
18 },
19 response::IntoResponse,
20};
21use axum_macros::FromRequestParts;
22use hyper::StatusCode;
23use mas_storage::pagination::PaginationDirection;
24use schemars::JsonSchema;
25use serde::Deserialize;
26use ulid::Ulid;
27
28use super::response::ErrorResponse;
29
30#[derive(Debug, thiserror::Error)]
31#[error("Invalid ULID in path")]
32pub struct UlidPathParamRejection(#[from] PathRejection);
33
34impl IntoResponse for UlidPathParamRejection {
35 fn into_response(self) -> axum::response::Response {
36 (
37 StatusCode::BAD_REQUEST,
38 Json(ErrorResponse::from_error(&self)),
39 )
40 .into_response()
41 }
42}
43
44#[derive(JsonSchema, Debug, Clone, Copy, Deserialize)]
45struct UlidInPath {
46 #[schemars(with = "super::schema::Ulid")]
48 id: Ulid,
49}
50
51#[derive(FromRequestParts, OperationIo, Debug, Clone, Copy)]
52#[from_request(rejection(UlidPathParamRejection))]
53#[aide(input_with = "Path<UlidInPath>")]
54pub struct UlidPathParam(#[from_request(via(Path))] UlidInPath);
55
56impl std::ops::Deref for UlidPathParam {
57 type Target = Ulid;
58
59 fn deref(&self) -> &Self::Target {
60 &self.0.id
61 }
62}
63
64const DEFAULT_PAGE_SIZE: usize = 10;
66
67#[derive(Deserialize, JsonSchema, Clone, Copy)]
68struct PaginationParams {
69 #[serde(rename = "page[before]")]
71 #[schemars(with = "Option<super::schema::Ulid>")]
72 before: Option<Ulid>,
73
74 #[serde(rename = "page[after]")]
76 #[schemars(with = "Option<super::schema::Ulid>")]
77 after: Option<Ulid>,
78
79 #[serde(rename = "page[first]")]
81 first: Option<NonZeroUsize>,
82
83 #[serde(rename = "page[last]")]
85 last: Option<NonZeroUsize>,
86}
87
88#[derive(Debug, thiserror::Error)]
89pub enum PaginationRejection {
90 #[error("Invalid pagination parameters")]
91 Invalid(#[from] QueryRejection),
92
93 #[error("Cannot specify both `page[first]` and `page[last]` parameters")]
94 FirstAndLast,
95}
96
97impl IntoResponse for PaginationRejection {
98 fn into_response(self) -> axum::response::Response {
99 (
100 StatusCode::BAD_REQUEST,
101 Json(ErrorResponse::from_error(&self)),
102 )
103 .into_response()
104 }
105}
106
107#[derive(OperationIo, Debug, Clone, Copy)]
109#[aide(input_with = "Query<PaginationParams>")]
110pub struct Pagination(pub mas_storage::Pagination);
111
112impl<S: Send + Sync> FromRequestParts<S> for Pagination {
113 type Rejection = PaginationRejection;
114
115 async fn from_request_parts(
116 parts: &mut axum::http::request::Parts,
117 state: &S,
118 ) -> Result<Self, Self::Rejection> {
119 let params = Query::<PaginationParams>::from_request_parts(parts, state).await?;
120
121 let (direction, count) = match (params.first, params.last) {
123 (Some(_), Some(_)) => return Err(PaginationRejection::FirstAndLast),
125
126 (None, None) => (PaginationDirection::Forward, DEFAULT_PAGE_SIZE),
128
129 (Some(first), None) => (PaginationDirection::Forward, first.into()),
130 (None, Some(last)) => (PaginationDirection::Backward, last.into()),
131 };
132
133 Ok(Self(mas_storage::Pagination {
134 before: params.before,
135 after: params.after,
136 direction,
137 count,
138 }))
139 }
140}