Skip to main content

reinhardt_rest/serializers/
validators.rs

1//! Validators for ModelSerializer
2//!
3//! This module provides validators for enforcing database constraints
4//! such as uniqueness of fields.
5//!
6//! # Examples
7//!
8//! ```no_run
9//! use reinhardt_rest::serializers::validators::{UniqueValidator, UniqueTogetherValidator};
10//! use reinhardt_db::orm::Model;
11//! use reinhardt_db::backends::DatabaseConnection;
12//! use serde::{Serialize, Deserialize};
13//!
14//! #[derive(Debug, Clone, Serialize, Deserialize)]
15//! struct User {
16//!     id: Option<i64>,
17//!     username: String,
18//!     email: String,
19//! }
20//!
21//! impl Model for User {
22//!     type PrimaryKey = i64;
23//!     type Fields = UserFields;
24//!     fn table_name() -> &'static str { "users" }
25//!     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
26//!     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
27//!     fn new_fields() -> Self::Fields { UserFields }
28//! }
29//! #[derive(Clone)]
30//! struct UserFields;
31//! impl reinhardt_db::orm::FieldSelector for UserFields {
32//!     fn with_alias(self, _alias: &str) -> Self { self }
33//! }
34//!
35//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
36//! let connection = DatabaseConnection::connect_postgres("postgres://localhost/test").await?;
37//!
38//! // Validate that username is unique
39//! let validator = UniqueValidator::<User>::new("username");
40//! validator.validate(&connection, "alice", None).await?;
41//!
42//! // Validate that (username, email) combination is unique
43//! let mut values = std::collections::HashMap::new();
44//! values.insert("username".to_string(), "alice".to_string());
45//! values.insert("email".to_string(), "alice@example.com".to_string());
46//!
47//! let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
48//! validator.validate(&connection, &values, None).await?;
49//! # Ok(())
50//! # }
51//! ```
52
53use super::SerializerError;
54use reinhardt_db::backends::DatabaseConnection;
55use reinhardt_db::orm::{Filter, FilterOperator, FilterValue, Model};
56use std::marker::PhantomData;
57use thiserror::Error;
58
59/// Errors that can occur during database validation
60#[derive(Debug, Error, Clone, PartialEq)]
61pub enum DatabaseValidatorError {
62	/// A unique constraint was violated for a single field
63	#[error("Unique constraint violated: {field} = '{value}' already exists in table {table}")]
64	UniqueConstraintViolation {
65		/// The field name that violated the constraint
66		field: String,
67		/// The value that caused the violation
68		value: String,
69		/// The table name
70		table: String,
71		/// Optional custom message
72		message: Option<String>,
73	},
74
75	/// A unique together constraint was violated for multiple fields
76	#[error(
77		"Unique together constraint violated: fields ({fields:?}) with values ({values:?}) already exist in table {table}"
78	)]
79	UniqueTogetherViolation {
80		/// The field names that violated the constraint
81		fields: Vec<String>,
82		/// The values that caused the violation
83		values: Vec<String>,
84		/// The table name
85		table: String,
86		/// Optional custom message
87		message: Option<String>,
88	},
89
90	/// A database error occurred during validation
91	#[error("Database error during validation: {message}")]
92	DatabaseError {
93		/// The error message from the database
94		message: String,
95		/// The SQL query that failed (optional, for debugging)
96		query: Option<String>,
97	},
98
99	/// A required field was not found in the data
100	#[error("Required field '{field}' not found in validation data")]
101	FieldNotFound {
102		/// The field name that was missing
103		field: String,
104	},
105}
106
107impl From<DatabaseValidatorError> for SerializerError {
108	fn from(err: DatabaseValidatorError) -> Self {
109		SerializerError::Other {
110			message: err.to_string(),
111		}
112	}
113}
114
115impl From<DatabaseValidatorError> for reinhardt_core::exception::Error {
116	fn from(err: DatabaseValidatorError) -> Self {
117		match err {
118			DatabaseValidatorError::UniqueConstraintViolation {
119				field,
120				value,
121				table,
122				message,
123			} => {
124				let msg = message.unwrap_or_else(|| {
125					format!(
126						"Field '{}' with value '{}' already exists in {}",
127						field, value, table
128					)
129				});
130				reinhardt_core::exception::Error::Conflict(msg)
131			}
132			DatabaseValidatorError::UniqueTogetherViolation {
133				fields,
134				values,
135				table,
136				message,
137			} => {
138				let msg = message.unwrap_or_else(|| {
139					format!(
140						"Combination of fields {:?} with values {:?} already exists in {}",
141						fields, values, table
142					)
143				});
144				reinhardt_core::exception::Error::Conflict(msg)
145			}
146			DatabaseValidatorError::FieldNotFound { field } => {
147				reinhardt_core::exception::Error::Validation(format!(
148					"Required field '{}' not found",
149					field
150				))
151			}
152			DatabaseValidatorError::DatabaseError { message, .. } => {
153				reinhardt_core::exception::Error::Database(message)
154			}
155		}
156	}
157}
158
159/// UniqueValidator ensures that a field value is unique in the database
160///
161/// This validator checks that a given field value doesn't already exist
162/// in the database table, with optional support for excluding the current
163/// instance during updates.
164///
165/// # Examples
166///
167/// ```no_run
168/// # use reinhardt_rest::serializers::validators::UniqueValidator;
169/// # use reinhardt_db::orm::Model;
170/// # use reinhardt_db::backends::DatabaseConnection;
171/// # use serde::{Serialize, Deserialize};
172/// #
173/// # #[derive(Debug, Clone, Serialize, Deserialize)]
174/// # struct User {
175/// #     id: Option<i64>,
176/// #     username: String,
177/// # }
178/// #
179/// # impl Model for User {
180/// #     type PrimaryKey = i64;
181/// #     type Fields = UserFields;
182/// #     fn table_name() -> &'static str { "users" }
183/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
184/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
185/// #     fn new_fields() -> Self::Fields { UserFields }
186/// # }
187/// # #[derive(Clone)]
188/// # struct UserFields;
189/// # impl reinhardt_db::orm::FieldSelector for UserFields {
190/// #     fn with_alias(self, _alias: &str) -> Self { self }
191/// # }
192/// #
193/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
194/// let connection = DatabaseConnection::connect_postgres("postgres://localhost/test").await?;
195/// let validator = UniqueValidator::<User>::new("username");
196///
197/// // Check if "alice" is unique
198/// validator.validate(&connection, "alice", None).await?;
199///
200/// // Check if "alice" is unique, excluding user with id=1
201/// let user_id = 1i64;
202/// validator.validate(&connection, "alice", Some(&user_id)).await?;
203/// # Ok(())
204/// # }
205/// ```
206#[derive(Debug, Clone)]
207pub struct UniqueValidator<M: Model> {
208	field_name: String,
209	message: Option<String>,
210	_phantom: PhantomData<M>,
211}
212
213impl<M: Model> UniqueValidator<M> {
214	/// Create a new UniqueValidator for the specified field
215	///
216	/// # Examples
217	///
218	/// ```
219	/// # use reinhardt_rest::serializers::validators::UniqueValidator;
220	/// # use reinhardt_db::orm::Model;
221	/// # use serde::{Serialize, Deserialize};
222	/// #
223	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
224	/// # struct User { id: Option<i64>, username: String }
225	/// #
226	/// # impl Model for User {
227	/// #     type PrimaryKey = i64;
228	/// #     type Fields = UserFields;
229	/// #     fn table_name() -> &'static str { "users" }
230	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
231	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
232	/// #     fn new_fields() -> Self::Fields { UserFields }
233	/// # }
234	/// # #[derive(Clone)]
235	/// # struct UserFields;
236	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
237	/// #     fn with_alias(self, _alias: &str) -> Self { self }
238	/// # }
239	/// let validator = UniqueValidator::<User>::new("username");
240	/// // Verify the validator is created successfully
241	/// let _: UniqueValidator<User> = validator;
242	/// ```
243	pub fn new(field_name: impl Into<String>) -> Self {
244		Self {
245			field_name: field_name.into(),
246			message: None,
247			_phantom: PhantomData,
248		}
249	}
250
251	/// Set a custom error message
252	///
253	/// # Examples
254	///
255	/// ```
256	/// # use reinhardt_rest::serializers::validators::UniqueValidator;
257	/// # use reinhardt_db::orm::Model;
258	/// # use serde::{Serialize, Deserialize};
259	/// #
260	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
261	/// # struct User { id: Option<i64>, username: String }
262	/// #
263	/// # impl Model for User {
264	/// #     type PrimaryKey = i64;
265	/// #     type Fields = UserFields;
266	/// #     fn table_name() -> &'static str { "users" }
267	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
268	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
269	/// #     fn new_fields() -> Self::Fields { UserFields }
270	/// # }
271	/// # #[derive(Clone)]
272	/// # struct UserFields;
273	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
274	/// #     fn with_alias(self, _alias: &str) -> Self { self }
275	/// # }
276	/// let validator = UniqueValidator::<User>::new("username")
277	///     .with_message("Username must be unique");
278	/// // Verify the validator is configured with custom message
279	/// let _: UniqueValidator<User> = validator;
280	/// ```
281	pub fn with_message(mut self, message: impl Into<String>) -> Self {
282		self.message = Some(message.into());
283		self
284	}
285
286	/// Get the field name being validated
287	pub fn field_name(&self) -> &str {
288		&self.field_name
289	}
290
291	pub async fn validate(
292		&self,
293		_connection: &DatabaseConnection,
294		value: &str,
295		instance_pk: Option<&M::PrimaryKey>,
296	) -> Result<(), DatabaseValidatorError>
297	where
298		M::PrimaryKey: std::fmt::Display,
299	{
300		let table_name = M::table_name();
301
302		// Build QuerySet with filter
303		let mut qs = M::objects().all();
304		qs = qs.filter(Filter::new(
305			self.field_name.clone(),
306			FilterOperator::Eq,
307			FilterValue::String(value.to_string()),
308		));
309
310		// Exclude current instance if updating
311		if let Some(pk) = instance_pk {
312			qs = qs.filter(Filter::new(
313				M::primary_key_field().to_string(),
314				FilterOperator::Ne,
315				FilterValue::String(pk.to_string()),
316			));
317		}
318
319		// Execute count query
320		let count = qs
321			.count()
322			.await
323			.map_err(|e| DatabaseValidatorError::DatabaseError {
324				message: e.to_string(),
325				query: None,
326			})?;
327
328		if count > 0 {
329			Err(DatabaseValidatorError::UniqueConstraintViolation {
330				field: self.field_name.clone(),
331				value: value.to_string(),
332				table: table_name.to_string(),
333				message: self.message.clone(),
334			})
335		} else {
336			Ok(())
337		}
338	}
339}
340
341/// UniqueTogetherValidator ensures that a combination of fields is unique
342///
343/// This validator checks that a combination of field values doesn't already exist
344/// in the database table, with optional support for excluding the current
345/// instance during updates.
346///
347/// # Examples
348///
349/// ```no_run
350/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
351/// # use reinhardt_db::orm::Model;
352/// # use reinhardt_db::backends::DatabaseConnection;
353/// # use serde::{Serialize, Deserialize};
354/// # use std::collections::HashMap;
355/// #
356/// # #[derive(Debug, Clone, Serialize, Deserialize)]
357/// # struct User {
358/// #     id: Option<i64>,
359/// #     username: String,
360/// #     email: String,
361/// # }
362/// #
363/// # impl Model for User {
364/// #     type PrimaryKey = i64;
365/// #     type Fields = UserFields;
366/// #     fn table_name() -> &'static str { "users" }
367/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
368/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
369/// #     fn new_fields() -> Self::Fields { UserFields }
370/// # }
371/// # #[derive(Clone)]
372/// # struct UserFields;
373/// # impl reinhardt_db::orm::FieldSelector for UserFields {
374/// #     fn with_alias(self, _alias: &str) -> Self { self }
375/// # }
376/// #
377/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
378/// let connection = DatabaseConnection::connect_postgres("postgres://localhost/test").await?;
379/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
380///
381/// let mut values = HashMap::new();
382/// values.insert("username".to_string(), "alice".to_string());
383/// values.insert("email".to_string(), "alice@example.com".to_string());
384///
385/// validator.validate(&connection, &values, None).await?;
386/// # Ok(())
387/// # }
388/// ```
389#[derive(Debug, Clone)]
390pub struct UniqueTogetherValidator<M: Model> {
391	field_names: Vec<String>,
392	message: Option<String>,
393	_phantom: PhantomData<M>,
394}
395
396impl<M: Model> UniqueTogetherValidator<M> {
397	/// Create a new UniqueTogetherValidator for the specified fields
398	///
399	/// # Examples
400	///
401	/// ```
402	/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
403	/// # use reinhardt_db::orm::Model;
404	/// # use serde::{Serialize, Deserialize};
405	/// #
406	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
407	/// # struct User { id: Option<i64>, username: String, email: String }
408	/// #
409	/// # impl Model for User {
410	/// #     type PrimaryKey = i64;
411	/// #     type Fields = UserFields;
412	/// #     fn table_name() -> &'static str { "users" }
413	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
414	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
415	/// #     fn new_fields() -> Self::Fields { UserFields }
416	/// # }
417	/// # #[derive(Clone)]
418	/// # struct UserFields;
419	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
420	/// #     fn with_alias(self, _alias: &str) -> Self { self }
421	/// # }
422	/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
423	/// // Verify the validator is created successfully
424	/// let _: UniqueTogetherValidator<User> = validator;
425	/// ```
426	pub fn new(field_names: Vec<impl Into<String>>) -> Self {
427		Self {
428			field_names: field_names.into_iter().map(|f| f.into()).collect(),
429			message: None,
430			_phantom: PhantomData,
431		}
432	}
433
434	/// Set a custom error message
435	///
436	/// # Examples
437	///
438	/// ```
439	/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
440	/// # use reinhardt_db::orm::Model;
441	/// # use serde::{Serialize, Deserialize};
442	/// #
443	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
444	/// # struct User { id: Option<i64>, username: String, email: String }
445	/// #
446	/// # impl Model for User {
447	/// #     type PrimaryKey = i64;
448	/// #     type Fields = UserFields;
449	/// #     fn table_name() -> &'static str { "users" }
450	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
451	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
452	/// #     fn new_fields() -> Self::Fields { UserFields }
453	/// # }
454	/// # #[derive(Clone)]
455	/// # struct UserFields;
456	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
457	/// #     fn with_alias(self, _alias: &str) -> Self { self }
458	/// # }
459	/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"])
460	///     .with_message("Username and email combination must be unique");
461	/// // Verify the validator is configured with custom message
462	/// let _: UniqueTogetherValidator<User> = validator;
463	/// ```
464	pub fn with_message(mut self, message: impl Into<String>) -> Self {
465		self.message = Some(message.into());
466		self
467	}
468
469	/// Get the field names being validated
470	pub fn field_names(&self) -> &[String] {
471		&self.field_names
472	}
473
474	pub async fn validate(
475		&self,
476		_connection: &DatabaseConnection,
477		values: &std::collections::HashMap<String, String>,
478		instance_pk: Option<&M::PrimaryKey>,
479	) -> Result<(), DatabaseValidatorError>
480	where
481		M::PrimaryKey: std::fmt::Display,
482	{
483		let table_name = M::table_name();
484
485		// Build QuerySet with filters for all fields
486		let mut qs = M::objects().all();
487		let mut field_values = Vec::new();
488
489		for field_name in &self.field_names {
490			let value =
491				values
492					.get(field_name)
493					.ok_or_else(|| DatabaseValidatorError::FieldNotFound {
494						field: field_name.clone(),
495					})?;
496			field_values.push(value.clone());
497
498			qs = qs.filter(Filter::new(
499				field_name.clone(),
500				FilterOperator::Eq,
501				FilterValue::String(value.clone()),
502			));
503		}
504
505		// Exclude current instance if updating
506		if let Some(pk) = instance_pk {
507			qs = qs.filter(Filter::new(
508				M::primary_key_field().to_string(),
509				FilterOperator::Ne,
510				FilterValue::String(pk.to_string()),
511			));
512		}
513
514		// Execute count query
515		let count = qs
516			.count()
517			.await
518			.map_err(|e| DatabaseValidatorError::DatabaseError {
519				message: e.to_string(),
520				query: None,
521			})?;
522
523		if count > 0 {
524			Err(DatabaseValidatorError::UniqueTogetherViolation {
525				fields: self.field_names.clone(),
526				values: field_values,
527				table: table_name.to_string(),
528				message: self.message.clone(),
529			})
530		} else {
531			Ok(())
532		}
533	}
534}
535
536#[cfg(test)]
537mod tests {
538	use super::*;
539	use reinhardt_db::orm::FieldSelector;
540
541	#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
542	struct TestUser {
543		id: Option<i64>,
544		username: String,
545		email: String,
546	}
547
548	#[derive(Debug, Clone)]
549	struct TestUserFields;
550
551	impl FieldSelector for TestUserFields {
552		fn with_alias(self, _alias: &str) -> Self {
553			self
554		}
555	}
556
557	impl Model for TestUser {
558		type PrimaryKey = i64;
559		type Fields = TestUserFields;
560
561		fn table_name() -> &'static str {
562			"test_users"
563		}
564
565		fn new_fields() -> Self::Fields {
566			TestUserFields
567		}
568
569		fn primary_key(&self) -> Option<Self::PrimaryKey> {
570			self.id
571		}
572
573		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
574			self.id = Some(value);
575		}
576	}
577
578	#[test]
579	fn test_unique_validator_new() {
580		let validator = UniqueValidator::<TestUser>::new("username");
581		assert_eq!(validator.field_name(), "username");
582	}
583
584	#[test]
585	fn test_unique_validator_with_message() {
586		let validator =
587			UniqueValidator::<TestUser>::new("username").with_message("Custom error message");
588		assert_eq!(validator.field_name(), "username");
589		assert!(validator.message.is_some());
590	}
591
592	#[test]
593	fn test_unique_together_validator_new() {
594		let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"]);
595		assert_eq!(validator.field_names().len(), 2);
596		assert_eq!(validator.field_names()[0], "username");
597		assert_eq!(validator.field_names()[1], "email");
598	}
599
600	#[test]
601	fn test_unique_together_validator_with_message() {
602		let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"])
603			.with_message("Custom combination message");
604		assert_eq!(validator.field_names().len(), 2);
605		assert!(validator.message.is_some());
606	}
607}