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