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