reinhardt_views/list.rs
1//! ListView for displaying lists of objects.
2
3use async_trait::async_trait;
4use reinhardt_core::exception::{Error, Result};
5use reinhardt_db::orm::Model;
6use reinhardt_http::{Request, Response};
7use reinhardt_rest::serializers::{JsonSerializer, Serializer};
8use serde::{Deserialize, Serialize};
9
10use crate::core::View;
11use crate::mixins::MultipleObjectMixin;
12
13/// ListView for displaying multiple objects
14pub struct ListView<T>
15where
16 T: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone,
17{
18 objects: Vec<T>,
19 ordering: Option<Vec<String>>,
20 paginate_by: Option<usize>,
21 allow_empty_flag: bool,
22 context_object_name: Option<String>,
23 serializer: Box<dyn Serializer<Input = T, Output = String> + Send + Sync>,
24}
25
26impl<T> Default for ListView<T>
27where
28 T: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone + 'static,
29{
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl<T> ListView<T>
36where
37 T: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone + 'static,
38{
39 /// Creates a new `ListView` with default settings.
40 ///
41 /// Uses `JsonSerializer` by default. Use `with_serializer` to provide a custom serializer.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use reinhardt_views::{ListView, MultipleObjectMixin};
47 /// use reinhardt_db::orm::Model;
48 /// use serde::{Serialize, Deserialize};
49 ///
50 /// #[derive(Debug, Clone, Serialize, Deserialize)]
51 /// struct Article {
52 /// id: Option<i64>,
53 /// title: String,
54 /// }
55 ///
56 /// #[derive(Clone)]
57 /// struct ArticleFields;
58 ///
59 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
60 /// fn with_alias(self, _alias: &str) -> Self {
61 /// self
62 /// }
63 /// }
64 ///
65 /// impl Model for Article {
66 /// type PrimaryKey = i64;
67 /// type Fields = ArticleFields;
68 /// type Objects = reinhardt_db::orm::Manager<Self>;
69 /// fn table_name() -> &'static str { "articles" }
70 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
71 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
72 /// fn new_fields() -> Self::Fields { ArticleFields }
73 /// }
74 ///
75 /// let view = ListView::<Article>::new();
76 /// assert!(view.get_context_object_name().is_none());
77 /// ```
78 pub fn new() -> Self {
79 Self {
80 objects: Vec::new(),
81 ordering: None,
82 paginate_by: None,
83 allow_empty_flag: true,
84 context_object_name: None,
85 serializer: Box::new(JsonSerializer::<T>::new()),
86 }
87 }
88
89 /// Sets a custom serializer for the view.
90 ///
91 /// # Examples
92 ///
93 /// ```
94 /// use reinhardt_views::{ListView, MultipleObjectMixin};
95 /// use reinhardt_rest::serializers::JsonSerializer;
96 /// use reinhardt_db::orm::Model;
97 /// use serde::{Serialize, Deserialize};
98 ///
99 /// #[derive(Debug, Clone, Serialize, Deserialize)]
100 /// struct Article {
101 /// id: Option<i64>,
102 /// title: String,
103 /// }
104 ///
105 /// #[derive(Clone)]
106 /// struct ArticleFields;
107 ///
108 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
109 /// fn with_alias(self, _alias: &str) -> Self {
110 /// self
111 /// }
112 /// }
113 ///
114 /// impl Model for Article {
115 /// type PrimaryKey = i64;
116 /// type Fields = ArticleFields;
117 /// type Objects = reinhardt_db::orm::Manager<Self>;
118 /// fn table_name() -> &'static str { "articles" }
119 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
120 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
121 /// fn new_fields() -> Self::Fields { ArticleFields }
122 /// }
123 ///
124 /// let view = ListView::<Article>::new()
125 /// .with_serializer(Box::new(JsonSerializer::<Article>::new()));
126 /// ```
127 pub fn with_serializer(
128 mut self,
129 serializer: Box<dyn Serializer<Input = T, Output = String> + Send + Sync>,
130 ) -> Self {
131 self.serializer = serializer;
132 self
133 }
134 /// Sets the list of objects to display in the view.
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// use reinhardt_views::{ListView, MultipleObjectMixin};
140 /// use reinhardt_db::orm::Model;
141 /// use serde::{Serialize, Deserialize};
142 ///
143 /// #[derive(Debug, Clone, Serialize, Deserialize)]
144 /// struct Article {
145 /// id: Option<i64>,
146 /// title: String,
147 /// }
148 ///
149 /// #[derive(Clone)]
150 /// struct ArticleFields;
151 ///
152 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
153 /// fn with_alias(self, _alias: &str) -> Self {
154 /// self
155 /// }
156 /// }
157 ///
158 /// impl Model for Article {
159 /// type PrimaryKey = i64;
160 /// type Fields = ArticleFields;
161 /// type Objects = reinhardt_db::orm::Manager<Self>;
162 /// fn table_name() -> &'static str { "articles" }
163 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
164 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
165 /// fn new_fields() -> Self::Fields { ArticleFields }
166 /// }
167 ///
168 /// let articles = vec![
169 /// Article { id: Some(1), title: "First".to_string() },
170 /// Article { id: Some(2), title: "Second".to_string() },
171 /// ];
172 ///
173 /// let view = ListView::<Article>::new()
174 /// .with_objects(articles.clone());
175 /// # tokio_test::block_on(async {
176 /// let objects = view.get_objects().await.unwrap();
177 /// assert_eq!(objects.len(), 2);
178 /// assert_eq!(objects[0].title, "First");
179 /// # });
180 /// ```
181 pub fn with_objects(mut self, objects: Vec<T>) -> Self {
182 self.objects = objects;
183 self
184 }
185 /// Sets the ordering for the object list.
186 ///
187 /// # Examples
188 ///
189 /// ```
190 /// use reinhardt_views::{ListView, MultipleObjectMixin};
191 /// use reinhardt_db::orm::Model;
192 /// use serde::{Serialize, Deserialize};
193 ///
194 /// #[derive(Debug, Clone, Serialize, Deserialize)]
195 /// struct Article {
196 /// id: Option<i64>,
197 /// title: String,
198 /// }
199 ///
200 /// #[derive(Clone)]
201 /// struct ArticleFields;
202 ///
203 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
204 /// fn with_alias(self, _alias: &str) -> Self {
205 /// self
206 /// }
207 /// }
208 ///
209 /// impl Model for Article {
210 /// type PrimaryKey = i64;
211 /// type Fields = ArticleFields;
212 /// type Objects = reinhardt_db::orm::Manager<Self>;
213 /// fn table_name() -> &'static str { "articles" }
214 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
215 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
216 /// fn new_fields() -> Self::Fields { ArticleFields }
217 /// }
218 ///
219 /// let view = ListView::<Article>::new()
220 /// .with_ordering(vec!["-created_at".to_string(), "title".to_string()]);
221 ///
222 /// assert_eq!(view.get_ordering(), Some(vec!["-created_at".to_string(), "title".to_string()]));
223 /// ```
224 pub fn with_ordering(mut self, ordering: Vec<String>) -> Self {
225 self.ordering = Some(ordering);
226 self
227 }
228 /// Sets the number of items per page.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use reinhardt_views::{ListView, MultipleObjectMixin};
234 /// use reinhardt_db::orm::Model;
235 /// use serde::{Serialize, Deserialize};
236 ///
237 /// #[derive(Debug, Clone, Serialize, Deserialize)]
238 /// struct Article {
239 /// id: Option<i64>,
240 /// title: String,
241 /// }
242 ///
243 /// #[derive(Clone)]
244 /// struct ArticleFields;
245 ///
246 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
247 /// fn with_alias(self, _alias: &str) -> Self {
248 /// self
249 /// }
250 /// }
251 ///
252 /// impl Model for Article {
253 /// type PrimaryKey = i64;
254 /// type Fields = ArticleFields;
255 /// type Objects = reinhardt_db::orm::Manager<Self>;
256 /// fn table_name() -> &'static str { "articles" }
257 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
258 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
259 /// fn new_fields() -> Self::Fields { ArticleFields }
260 /// }
261 ///
262 /// let view = ListView::<Article>::new()
263 /// .with_paginate_by(25);
264 ///
265 /// assert_eq!(view.get_paginate_by(), Some(25));
266 /// ```
267 pub fn with_paginate_by(mut self, paginate_by: usize) -> Self {
268 self.paginate_by = Some(paginate_by);
269 self
270 }
271 /// Sets whether to allow empty result sets.
272 ///
273 /// When set to `false`, the view will return an error if no objects are found.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// use reinhardt_views::{ListView, MultipleObjectMixin};
279 /// use reinhardt_db::orm::Model;
280 /// use serde::{Serialize, Deserialize};
281 ///
282 /// #[derive(Debug, Clone, Serialize, Deserialize)]
283 /// struct Article {
284 /// id: Option<i64>,
285 /// title: String,
286 /// }
287 ///
288 /// #[derive(Clone)]
289 /// struct ArticleFields;
290 ///
291 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
292 /// fn with_alias(self, _alias: &str) -> Self {
293 /// self
294 /// }
295 /// }
296 ///
297 /// impl Model for Article {
298 /// type PrimaryKey = i64;
299 /// type Fields = ArticleFields;
300 /// type Objects = reinhardt_db::orm::Manager<Self>;
301 /// fn table_name() -> &'static str { "articles" }
302 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
303 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
304 /// fn new_fields() -> Self::Fields { ArticleFields }
305 /// }
306 ///
307 /// let view = ListView::<Article>::new()
308 /// .with_allow_empty(false);
309 ///
310 /// assert!(!view.allow_empty());
311 /// ```
312 pub fn with_allow_empty(mut self, allow_empty: bool) -> Self {
313 self.allow_empty_flag = allow_empty;
314 self
315 }
316 /// Sets a custom name for the object list in the context.
317 ///
318 /// # Examples
319 ///
320 /// ```
321 /// use reinhardt_views::{ListView, MultipleObjectMixin};
322 /// use reinhardt_db::orm::Model;
323 /// use serde::{Serialize, Deserialize};
324 ///
325 /// #[derive(Debug, Clone, Serialize, Deserialize)]
326 /// struct Article {
327 /// id: Option<i64>,
328 /// title: String,
329 /// }
330 ///
331 /// #[derive(Clone)]
332 /// struct ArticleFields;
333 ///
334 /// impl reinhardt_db::orm::FieldSelector for ArticleFields {
335 /// fn with_alias(self, _alias: &str) -> Self {
336 /// self
337 /// }
338 /// }
339 ///
340 /// impl Model for Article {
341 /// type PrimaryKey = i64;
342 /// type Fields = ArticleFields;
343 /// type Objects = reinhardt_db::orm::Manager<Self>;
344 /// fn table_name() -> &'static str { "articles" }
345 /// fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
346 /// fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
347 /// fn new_fields() -> Self::Fields { ArticleFields }
348 /// }
349 ///
350 /// let view = ListView::<Article>::new()
351 /// .with_context_object_name("articles");
352 ///
353 /// assert_eq!(view.get_context_object_name(), Some("articles"));
354 /// ```
355 pub fn with_context_object_name(mut self, name: impl Into<String>) -> Self {
356 self.context_object_name = Some(name.into());
357 self
358 }
359}
360
361#[async_trait]
362impl<T> MultipleObjectMixin<T> for ListView<T>
363where
364 T: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone + 'static,
365{
366 async fn get_objects(&self) -> Result<Vec<T>> {
367 Ok(self.objects.clone())
368 }
369
370 fn get_ordering(&self) -> Option<Vec<String>> {
371 self.ordering.clone()
372 }
373
374 fn allow_empty(&self) -> bool {
375 self.allow_empty_flag
376 }
377
378 fn get_paginate_by(&self) -> Option<usize> {
379 self.paginate_by
380 }
381
382 fn get_context_object_name(&self) -> Option<&str> {
383 self.context_object_name.as_deref()
384 }
385}
386
387#[async_trait]
388impl<T> View for ListView<T>
389where
390 T: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone + 'static,
391{
392 async fn dispatch(&self, request: Request) -> Result<Response> {
393 // Handle OPTIONS method
394 if request.method == "OPTIONS" {
395 let methods = self.allowed_methods().join(", ");
396 return Ok(Response::ok()
397 .with_header("Allow", &methods)
398 .with_header("Content-Type", "application/json"));
399 }
400
401 // Support GET and HEAD methods
402 let is_head = request.method == "HEAD";
403 if !matches!(request.method.as_str(), "GET" | "HEAD") {
404 return Err(Error::MethodNotAllowed(format!(
405 "Method {} not allowed",
406 request.method
407 )));
408 }
409
410 // Get objects
411 let mut object_list = self.get_objects().await?;
412
413 // Check if empty is allowed
414 if !self.allow_empty() && object_list.is_empty() {
415 return Err(Error::NotFound(
416 "Empty list and allow_empty is false".to_string(),
417 ));
418 }
419
420 // Apply ordering if configured
421 if let Some(ordering_fields) = self.get_ordering() {
422 // Sort by each field in reverse order (last field is primary sort)
423 for field in ordering_fields.iter().rev() {
424 let (field_name, descending) = if let Some(stripped) = field.strip_prefix('-') {
425 (stripped, true)
426 } else {
427 (field.as_str(), false)
428 };
429
430 // Use serde_json::Value for dynamic field comparison
431 object_list.sort_by(|a, b| {
432 let a_val = serde_json::to_value(a).unwrap_or(serde_json::Value::Null);
433 let b_val = serde_json::to_value(b).unwrap_or(serde_json::Value::Null);
434
435 // Extract field value from JSON
436 let a_field = a_val.get(field_name).unwrap_or(&serde_json::Value::Null);
437 let b_field = b_val.get(field_name).unwrap_or(&serde_json::Value::Null);
438
439 // Compare based on value type
440 let cmp = match (a_field, b_field) {
441 (serde_json::Value::String(a), serde_json::Value::String(b)) => a.cmp(b),
442 (serde_json::Value::Number(a), serde_json::Value::Number(b)) => {
443 // Compare as f64 for numeric values
444 let a_num = a.as_f64().unwrap_or(0.0);
445 let b_num = b.as_f64().unwrap_or(0.0);
446 a_num
447 .partial_cmp(&b_num)
448 .unwrap_or(std::cmp::Ordering::Equal)
449 }
450 (serde_json::Value::Bool(a), serde_json::Value::Bool(b)) => a.cmp(b),
451 (serde_json::Value::Null, serde_json::Value::Null) => {
452 std::cmp::Ordering::Equal
453 }
454 (serde_json::Value::Null, _) => std::cmp::Ordering::Less,
455 (_, serde_json::Value::Null) => std::cmp::Ordering::Greater,
456 _ => std::cmp::Ordering::Equal,
457 };
458
459 if descending { cmp.reverse() } else { cmp }
460 });
461 }
462 }
463
464 // Apply pagination if configured
465 let total_count = object_list.len();
466 let (paginated_objects, pagination_metadata) =
467 if let Some(page_size) = self.get_paginate_by() {
468 // Parse page number from query params (default to 1)
469 let page: usize = request
470 .query_params
471 .get("page")
472 .and_then(|p| p.parse().ok())
473 .unwrap_or(1);
474
475 // Validate page number
476 let page = if page < 1 { 1 } else { page };
477
478 // Calculate pagination
479 let start = (page - 1) * page_size;
480 let end = start + page_size;
481
482 // Apply pagination
483 let paginated = if start < object_list.len() {
484 object_list[start..end.min(object_list.len())].to_vec()
485 } else {
486 Vec::new()
487 };
488
489 // Build pagination metadata
490 let total_pages = total_count.div_ceil(page_size); // Ceiling division
491 let has_next = page < total_pages;
492 let has_previous = page > 1;
493
494 let metadata = serde_json::json!({
495 "count": total_count,
496 "page": page,
497 "page_size": page_size,
498 "total_pages": total_pages,
499 "next": if has_next { Some(page + 1) } else { None },
500 "previous": if has_previous { Some(page - 1) } else { None },
501 });
502
503 (paginated, Some(metadata))
504 } else {
505 (object_list, None)
506 };
507
508 // Serialize objects
509 let serialized_objects: Result<Vec<_>> = paginated_objects
510 .iter()
511 .map(|obj| {
512 self.serializer.serialize(obj).map_err(|e| match e {
513 reinhardt_rest::serializers::SerializerError::Validation(v) => {
514 Error::Validation(v.to_string())
515 }
516 reinhardt_rest::serializers::SerializerError::Serde { message } => {
517 Error::Serialization(message)
518 }
519 reinhardt_rest::serializers::SerializerError::Other { message } => {
520 Error::Serialization(message)
521 }
522 _ => Error::Serialization(e.to_string()),
523 })
524 })
525 .collect();
526
527 let serialized_objects = serialized_objects?;
528
529 // Build response - for HEAD, return same headers but empty body
530 if is_head {
531 Ok(Response::ok().with_header("Content-Type", "application/json"))
532 } else {
533 // If pagination is enabled, wrap results in DRF-style format
534 if let Some(metadata) = pagination_metadata {
535 let response_data = serde_json::json!({
536 "count": metadata["count"],
537 "page": metadata["page"],
538 "page_size": metadata["page_size"],
539 "total_pages": metadata["total_pages"],
540 "next": metadata["next"],
541 "previous": metadata["previous"],
542 "results": serialized_objects
543 });
544 Response::ok().with_json(&response_data)
545 } else {
546 Response::ok().with_json(&serialized_objects)
547 }
548 }
549 }
550}