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