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}