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