Skip to main content

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}