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	/// Validates that the given value is unique for this field in the database.
292	pub async fn validate(
293		&self,
294		_connection: &DatabaseConnection,
295		value: &str,
296		instance_pk: Option<&M::PrimaryKey>,
297	) -> Result<(), DatabaseValidatorError>
298	where
299		M::PrimaryKey: std::fmt::Display,
300	{
301		let table_name = M::table_name();
302
303		// Build QuerySet with filter
304		let mut qs = M::objects().all();
305		qs = qs.filter(Filter::new(
306			self.field_name.clone(),
307			FilterOperator::Eq,
308			FilterValue::String(value.to_string()),
309		));
310
311		// Exclude current instance if updating
312		if let Some(pk) = instance_pk {
313			qs = qs.filter(Filter::new(
314				M::primary_key_field().to_string(),
315				FilterOperator::Ne,
316				FilterValue::String(pk.to_string()),
317			));
318		}
319
320		// Execute count query
321		let count = qs
322			.count()
323			.await
324			.map_err(|e| DatabaseValidatorError::DatabaseError {
325				message: e.to_string(),
326				query: None,
327			})?;
328
329		if count > 0 {
330			Err(DatabaseValidatorError::UniqueConstraintViolation {
331				field: self.field_name.clone(),
332				value: value.to_string(),
333				table: table_name.to_string(),
334				message: self.message.clone(),
335			})
336		} else {
337			Ok(())
338		}
339	}
340}
341
342/// UniqueTogetherValidator ensures that a combination of fields is unique
343///
344/// This validator checks that a combination of field values doesn't already exist
345/// in the database table, with optional support for excluding the current
346/// instance during updates.
347///
348/// # Examples
349///
350/// ```no_run
351/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
352/// # use reinhardt_db::orm::Model;
353/// # use reinhardt_db::backends::DatabaseConnection;
354/// # use serde::{Serialize, Deserialize};
355/// # use std::collections::HashMap;
356/// #
357/// # #[derive(Debug, Clone, Serialize, Deserialize)]
358/// # struct User {
359/// #     id: Option<i64>,
360/// #     username: String,
361/// #     email: String,
362/// # }
363/// #
364/// # impl Model for User {
365/// #     type PrimaryKey = i64;
366/// #     type Fields = UserFields;
367/// #     fn table_name() -> &'static str { "users" }
368/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
369/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
370/// #     fn new_fields() -> Self::Fields { UserFields }
371/// # }
372/// # #[derive(Clone)]
373/// # struct UserFields;
374/// # impl reinhardt_db::orm::FieldSelector for UserFields {
375/// #     fn with_alias(self, _alias: &str) -> Self { self }
376/// # }
377/// #
378/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
379/// let connection = DatabaseConnection::connect_postgres("postgres://localhost/test").await?;
380/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
381///
382/// let mut values = HashMap::new();
383/// values.insert("username".to_string(), "alice".to_string());
384/// values.insert("email".to_string(), "alice@example.com".to_string());
385///
386/// validator.validate(&connection, &values, None).await?;
387/// # Ok(())
388/// # }
389/// ```
390#[derive(Debug, Clone)]
391pub struct UniqueTogetherValidator<M: Model> {
392	field_names: Vec<String>,
393	message: Option<String>,
394	_phantom: PhantomData<M>,
395}
396
397impl<M: Model> UniqueTogetherValidator<M> {
398	/// Create a new UniqueTogetherValidator for the specified fields
399	///
400	/// # Examples
401	///
402	/// ```
403	/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
404	/// # use reinhardt_db::orm::Model;
405	/// # use serde::{Serialize, Deserialize};
406	/// #
407	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
408	/// # struct User { id: Option<i64>, username: String, email: String }
409	/// #
410	/// # impl Model for User {
411	/// #     type PrimaryKey = i64;
412	/// #     type Fields = UserFields;
413	/// #     fn table_name() -> &'static str { "users" }
414	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
415	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
416	/// #     fn new_fields() -> Self::Fields { UserFields }
417	/// # }
418	/// # #[derive(Clone)]
419	/// # struct UserFields;
420	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
421	/// #     fn with_alias(self, _alias: &str) -> Self { self }
422	/// # }
423	/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
424	/// // Verify the validator is created successfully
425	/// let _: UniqueTogetherValidator<User> = validator;
426	/// ```
427	pub fn new(field_names: Vec<impl Into<String>>) -> Self {
428		Self {
429			field_names: field_names.into_iter().map(|f| f.into()).collect(),
430			message: None,
431			_phantom: PhantomData,
432		}
433	}
434
435	/// Set a custom error message
436	///
437	/// # Examples
438	///
439	/// ```
440	/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
441	/// # use reinhardt_db::orm::Model;
442	/// # use serde::{Serialize, Deserialize};
443	/// #
444	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
445	/// # struct User { id: Option<i64>, username: String, email: String }
446	/// #
447	/// # impl Model for User {
448	/// #     type PrimaryKey = i64;
449	/// #     type Fields = UserFields;
450	/// #     fn table_name() -> &'static str { "users" }
451	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
452	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
453	/// #     fn new_fields() -> Self::Fields { UserFields }
454	/// # }
455	/// # #[derive(Clone)]
456	/// # struct UserFields;
457	/// # impl reinhardt_db::orm::FieldSelector for UserFields {
458	/// #     fn with_alias(self, _alias: &str) -> Self { self }
459	/// # }
460	/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"])
461	///     .with_message("Username and email combination must be unique");
462	/// // Verify the validator is configured with custom message
463	/// let _: UniqueTogetherValidator<User> = validator;
464	/// ```
465	pub fn with_message(mut self, message: impl Into<String>) -> Self {
466		self.message = Some(message.into());
467		self
468	}
469
470	/// Get the field names being validated
471	pub fn field_names(&self) -> &[String] {
472		&self.field_names
473	}
474
475	/// Validates that the combination of field values is unique together in the database.
476	pub async fn validate(
477		&self,
478		_connection: &DatabaseConnection,
479		values: &std::collections::HashMap<String, String>,
480		instance_pk: Option<&M::PrimaryKey>,
481	) -> Result<(), DatabaseValidatorError>
482	where
483		M::PrimaryKey: std::fmt::Display,
484	{
485		let table_name = M::table_name();
486
487		// Build QuerySet with filters for all fields
488		let mut qs = M::objects().all();
489		let mut field_values = Vec::new();
490
491		for field_name in &self.field_names {
492			let value =
493				values
494					.get(field_name)
495					.ok_or_else(|| DatabaseValidatorError::FieldNotFound {
496						field: field_name.clone(),
497					})?;
498			field_values.push(value.clone());
499
500			qs = qs.filter(Filter::new(
501				field_name.clone(),
502				FilterOperator::Eq,
503				FilterValue::String(value.clone()),
504			));
505		}
506
507		// Exclude current instance if updating
508		if let Some(pk) = instance_pk {
509			qs = qs.filter(Filter::new(
510				M::primary_key_field().to_string(),
511				FilterOperator::Ne,
512				FilterValue::String(pk.to_string()),
513			));
514		}
515
516		// Execute count query
517		let count = qs
518			.count()
519			.await
520			.map_err(|e| DatabaseValidatorError::DatabaseError {
521				message: e.to_string(),
522				query: None,
523			})?;
524
525		if count > 0 {
526			Err(DatabaseValidatorError::UniqueTogetherViolation {
527				fields: self.field_names.clone(),
528				values: field_values,
529				table: table_name.to_string(),
530				message: self.message.clone(),
531			})
532		} else {
533			Ok(())
534		}
535	}
536}
537
538#[cfg(test)]
539mod tests {
540	use super::*;
541	use reinhardt_db::orm::FieldSelector;
542
543	#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
544	struct TestUser {
545		id: Option<i64>,
546		username: String,
547		email: String,
548	}
549
550	#[derive(Debug, Clone)]
551	struct TestUserFields;
552
553	impl FieldSelector for TestUserFields {
554		fn with_alias(self, _alias: &str) -> Self {
555			self
556		}
557	}
558
559	impl Model for TestUser {
560		type PrimaryKey = i64;
561		type Fields = TestUserFields;
562
563		fn table_name() -> &'static str {
564			"test_users"
565		}
566
567		fn new_fields() -> Self::Fields {
568			TestUserFields
569		}
570
571		fn primary_key(&self) -> Option<Self::PrimaryKey> {
572			self.id
573		}
574
575		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
576			self.id = Some(value);
577		}
578	}
579
580	#[test]
581	fn test_unique_validator_new() {
582		let validator = UniqueValidator::<TestUser>::new("username");
583		assert_eq!(validator.field_name(), "username");
584	}
585
586	#[test]
587	fn test_unique_validator_with_message() {
588		let validator =
589			UniqueValidator::<TestUser>::new("username").with_message("Custom error message");
590		assert_eq!(validator.field_name(), "username");
591		assert!(validator.message.is_some());
592	}
593
594	#[test]
595	fn test_unique_together_validator_new() {
596		let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"]);
597		assert_eq!(validator.field_names().len(), 2);
598		assert_eq!(validator.field_names()[0], "username");
599		assert_eq!(validator.field_names()[1], "email");
600	}
601
602	#[test]
603	fn test_unique_together_validator_with_message() {
604		let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"])
605			.with_message("Custom combination message");
606		assert_eq!(validator.field_names().len(), 2);
607		assert!(validator.message.is_some());
608	}
609}