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