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}