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}