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	///     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}