mas_handlers/admin/
response.rs1#![allow(clippy::module_name_repetitions)]
8
9use mas_storage::Pagination;
10use schemars::JsonSchema;
11use serde::Serialize;
12use ulid::Ulid;
13
14use super::model::Resource;
15
16#[derive(Serialize, JsonSchema)]
18struct PaginationLinks {
19 #[serde(rename = "self")]
21 self_: String,
22
23 first: String,
25
26 last: String,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
33 next: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
39 prev: Option<String>,
40}
41
42#[derive(Serialize, JsonSchema)]
43struct PaginationMeta {
44 count: usize,
46}
47
48#[derive(Serialize, JsonSchema)]
50pub struct PaginatedResponse<T> {
51 meta: PaginationMeta,
53
54 data: Vec<SingleResource<T>>,
56
57 links: PaginationLinks,
59}
60
61fn url_with_pagination(base: &str, pagination: Pagination) -> String {
62 let (path, query) = base.split_once('?').unwrap_or((base, ""));
63 let mut query = query.to_owned();
64
65 if let Some(before) = pagination.before {
66 query = format!("{query}&page[before]={before}");
67 }
68
69 if let Some(after) = pagination.after {
70 query = format!("{query}&page[after]={after}");
71 }
72
73 let count = pagination.count;
74 match pagination.direction {
75 mas_storage::pagination::PaginationDirection::Forward => {
76 query = format!("{query}&page[first]={count}");
77 }
78 mas_storage::pagination::PaginationDirection::Backward => {
79 query = format!("{query}&page[last]={count}");
80 }
81 }
82
83 let query = query.trim_start_matches('&');
85
86 format!("{path}?{query}")
87}
88
89impl<T: Resource> PaginatedResponse<T> {
90 pub fn new(
91 page: mas_storage::Page<T>,
92 current_pagination: Pagination,
93 count: usize,
94 base: &str,
95 ) -> Self {
96 let links = PaginationLinks {
97 self_: url_with_pagination(base, current_pagination),
98 first: url_with_pagination(base, Pagination::first(current_pagination.count)),
99 last: url_with_pagination(base, Pagination::last(current_pagination.count)),
100 next: page.has_next_page.then(|| {
101 url_with_pagination(
102 base,
103 current_pagination
104 .clear_before()
105 .after(page.edges.last().unwrap().id()),
106 )
107 }),
108 prev: if page.has_previous_page {
109 Some(url_with_pagination(
110 base,
111 current_pagination
112 .clear_after()
113 .before(page.edges.first().unwrap().id()),
114 ))
115 } else {
116 None
117 },
118 };
119
120 let data = page.edges.into_iter().map(SingleResource::new).collect();
121
122 Self {
123 meta: PaginationMeta { count },
124 data,
125 links,
126 }
127 }
128}
129
130#[derive(Serialize, JsonSchema)]
132struct SingleResource<T> {
133 #[serde(rename = "type")]
135 type_: &'static str,
136
137 #[schemars(with = "super::schema::Ulid")]
139 id: Ulid,
140
141 attributes: T,
143
144 links: SelfLinks,
146}
147
148impl<T: Resource> SingleResource<T> {
149 fn new(resource: T) -> Self {
150 let self_ = resource.path();
151 Self {
152 type_: T::KIND,
153 id: resource.id(),
154 attributes: resource,
155 links: SelfLinks { self_ },
156 }
157 }
158}
159
160#[derive(Serialize, JsonSchema)]
162struct SelfLinks {
163 #[serde(rename = "self")]
165 self_: String,
166}
167
168#[derive(Serialize, JsonSchema)]
170pub struct SingleResponse<T> {
171 data: SingleResource<T>,
172 links: SelfLinks,
173}
174
175impl<T: Resource> SingleResponse<T> {
176 pub fn new(resource: T, self_: String) -> Self {
178 Self {
179 data: SingleResource::new(resource),
180 links: SelfLinks { self_ },
181 }
182 }
183
184 pub fn new_canonical(resource: T) -> Self {
186 let self_ = resource.path();
187 Self::new(resource, self_)
188 }
189}
190
191#[derive(Serialize, JsonSchema)]
193struct Error {
194 title: String,
196}
197
198impl Error {
199 fn from_error(error: &(dyn std::error::Error + 'static)) -> Self {
200 Self {
201 title: error.to_string(),
202 }
203 }
204}
205
206#[derive(Serialize, JsonSchema)]
208pub struct ErrorResponse {
209 errors: Vec<Error>,
211}
212
213impl ErrorResponse {
214 pub fn from_error(error: &(dyn std::error::Error + 'static)) -> Self {
216 let mut errors = Vec::new();
217 let mut head = Some(error);
218 while let Some(error) = head {
219 errors.push(Error::from_error(error));
220 head = error.source();
221 }
222 Self { errors }
223 }
224}