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 pub async fn validate(
292 &self,
293 _connection: &DatabaseConnection,
294 value: &str,
295 instance_pk: Option<&M::PrimaryKey>,
296 ) -> Result<(), DatabaseValidatorError>
297 where
298 M::PrimaryKey: std::fmt::Display,
299 {
300 let table_name = M::table_name();
301
302 // Build QuerySet with filter
303 let mut qs = M::objects().all();
304 qs = qs.filter(Filter::new(
305 self.field_name.clone(),
306 FilterOperator::Eq,
307 FilterValue::String(value.to_string()),
308 ));
309
310 // Exclude current instance if updating
311 if let Some(pk) = instance_pk {
312 qs = qs.filter(Filter::new(
313 M::primary_key_field().to_string(),
314 FilterOperator::Ne,
315 FilterValue::String(pk.to_string()),
316 ));
317 }
318
319 // Execute count query
320 let count = qs
321 .count()
322 .await
323 .map_err(|e| DatabaseValidatorError::DatabaseError {
324 message: e.to_string(),
325 query: None,
326 })?;
327
328 if count > 0 {
329 Err(DatabaseValidatorError::UniqueConstraintViolation {
330 field: self.field_name.clone(),
331 value: value.to_string(),
332 table: table_name.to_string(),
333 message: self.message.clone(),
334 })
335 } else {
336 Ok(())
337 }
338 }
339}
340
341/// UniqueTogetherValidator ensures that a combination of fields is unique
342///
343/// This validator checks that a combination of field values doesn't already exist
344/// in the database table, with optional support for excluding the current
345/// instance during updates.
346///
347/// # Examples
348///
349/// ```no_run
350/// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
351/// # use reinhardt_db::orm::Model;
352/// # use reinhardt_db::backends::DatabaseConnection;
353/// # use serde::{Serialize, Deserialize};
354/// # use std::collections::HashMap;
355/// #
356/// # #[derive(Debug, Clone, Serialize, Deserialize)]
357/// # struct User {
358/// # id: Option<i64>,
359/// # username: String,
360/// # email: String,
361/// # }
362/// #
363/// # impl Model for User {
364/// # type PrimaryKey = i64;
365/// # type Fields = UserFields;
366/// # fn table_name() -> &'static str { "users" }
367/// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
368/// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
369/// # fn new_fields() -> Self::Fields { UserFields }
370/// # }
371/// # #[derive(Clone)]
372/// # struct UserFields;
373/// # impl reinhardt_db::orm::FieldSelector for UserFields {
374/// # fn with_alias(self, _alias: &str) -> Self { self }
375/// # }
376/// #
377/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
378/// let connection = DatabaseConnection::connect_postgres("postgres://localhost/test").await?;
379/// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
380///
381/// let mut values = HashMap::new();
382/// values.insert("username".to_string(), "alice".to_string());
383/// values.insert("email".to_string(), "alice@example.com".to_string());
384///
385/// validator.validate(&connection, &values, None).await?;
386/// # Ok(())
387/// # }
388/// ```
389#[derive(Debug, Clone)]
390pub struct UniqueTogetherValidator<M: Model> {
391 field_names: Vec<String>,
392 message: Option<String>,
393 _phantom: PhantomData<M>,
394}
395
396impl<M: Model> UniqueTogetherValidator<M> {
397 /// Create a new UniqueTogetherValidator for the specified fields
398 ///
399 /// # Examples
400 ///
401 /// ```
402 /// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
403 /// # use reinhardt_db::orm::Model;
404 /// # use serde::{Serialize, Deserialize};
405 /// #
406 /// # #[derive(Debug, Clone, Serialize, Deserialize)]
407 /// # struct User { id: Option<i64>, username: String, email: String }
408 /// #
409 /// # impl Model for User {
410 /// # type PrimaryKey = i64;
411 /// # type Fields = UserFields;
412 /// # fn table_name() -> &'static str { "users" }
413 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
414 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
415 /// # fn new_fields() -> Self::Fields { UserFields }
416 /// # }
417 /// # #[derive(Clone)]
418 /// # struct UserFields;
419 /// # impl reinhardt_db::orm::FieldSelector for UserFields {
420 /// # fn with_alias(self, _alias: &str) -> Self { self }
421 /// # }
422 /// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"]);
423 /// // Verify the validator is created successfully
424 /// let _: UniqueTogetherValidator<User> = validator;
425 /// ```
426 pub fn new(field_names: Vec<impl Into<String>>) -> Self {
427 Self {
428 field_names: field_names.into_iter().map(|f| f.into()).collect(),
429 message: None,
430 _phantom: PhantomData,
431 }
432 }
433
434 /// Set a custom error message
435 ///
436 /// # Examples
437 ///
438 /// ```
439 /// # use reinhardt_rest::serializers::validators::UniqueTogetherValidator;
440 /// # use reinhardt_db::orm::Model;
441 /// # use serde::{Serialize, Deserialize};
442 /// #
443 /// # #[derive(Debug, Clone, Serialize, Deserialize)]
444 /// # struct User { id: Option<i64>, username: String, email: String }
445 /// #
446 /// # impl Model for User {
447 /// # type PrimaryKey = i64;
448 /// # type Fields = UserFields;
449 /// # fn table_name() -> &'static str { "users" }
450 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
451 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
452 /// # fn new_fields() -> Self::Fields { UserFields }
453 /// # }
454 /// # #[derive(Clone)]
455 /// # struct UserFields;
456 /// # impl reinhardt_db::orm::FieldSelector for UserFields {
457 /// # fn with_alias(self, _alias: &str) -> Self { self }
458 /// # }
459 /// let validator = UniqueTogetherValidator::<User>::new(vec!["username", "email"])
460 /// .with_message("Username and email combination must be unique");
461 /// // Verify the validator is configured with custom message
462 /// let _: UniqueTogetherValidator<User> = validator;
463 /// ```
464 pub fn with_message(mut self, message: impl Into<String>) -> Self {
465 self.message = Some(message.into());
466 self
467 }
468
469 /// Get the field names being validated
470 pub fn field_names(&self) -> &[String] {
471 &self.field_names
472 }
473
474 pub async fn validate(
475 &self,
476 _connection: &DatabaseConnection,
477 values: &std::collections::HashMap<String, String>,
478 instance_pk: Option<&M::PrimaryKey>,
479 ) -> Result<(), DatabaseValidatorError>
480 where
481 M::PrimaryKey: std::fmt::Display,
482 {
483 let table_name = M::table_name();
484
485 // Build QuerySet with filters for all fields
486 let mut qs = M::objects().all();
487 let mut field_values = Vec::new();
488
489 for field_name in &self.field_names {
490 let value =
491 values
492 .get(field_name)
493 .ok_or_else(|| DatabaseValidatorError::FieldNotFound {
494 field: field_name.clone(),
495 })?;
496 field_values.push(value.clone());
497
498 qs = qs.filter(Filter::new(
499 field_name.clone(),
500 FilterOperator::Eq,
501 FilterValue::String(value.clone()),
502 ));
503 }
504
505 // Exclude current instance if updating
506 if let Some(pk) = instance_pk {
507 qs = qs.filter(Filter::new(
508 M::primary_key_field().to_string(),
509 FilterOperator::Ne,
510 FilterValue::String(pk.to_string()),
511 ));
512 }
513
514 // Execute count query
515 let count = qs
516 .count()
517 .await
518 .map_err(|e| DatabaseValidatorError::DatabaseError {
519 message: e.to_string(),
520 query: None,
521 })?;
522
523 if count > 0 {
524 Err(DatabaseValidatorError::UniqueTogetherViolation {
525 fields: self.field_names.clone(),
526 values: field_values,
527 table: table_name.to_string(),
528 message: self.message.clone(),
529 })
530 } else {
531 Ok(())
532 }
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use reinhardt_db::orm::FieldSelector;
540
541 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
542 struct TestUser {
543 id: Option<i64>,
544 username: String,
545 email: String,
546 }
547
548 #[derive(Debug, Clone)]
549 struct TestUserFields;
550
551 impl FieldSelector for TestUserFields {
552 fn with_alias(self, _alias: &str) -> Self {
553 self
554 }
555 }
556
557 impl Model for TestUser {
558 type PrimaryKey = i64;
559 type Fields = TestUserFields;
560
561 fn table_name() -> &'static str {
562 "test_users"
563 }
564
565 fn new_fields() -> Self::Fields {
566 TestUserFields
567 }
568
569 fn primary_key(&self) -> Option<Self::PrimaryKey> {
570 self.id
571 }
572
573 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
574 self.id = Some(value);
575 }
576 }
577
578 #[test]
579 fn test_unique_validator_new() {
580 let validator = UniqueValidator::<TestUser>::new("username");
581 assert_eq!(validator.field_name(), "username");
582 }
583
584 #[test]
585 fn test_unique_validator_with_message() {
586 let validator =
587 UniqueValidator::<TestUser>::new("username").with_message("Custom error message");
588 assert_eq!(validator.field_name(), "username");
589 assert!(validator.message.is_some());
590 }
591
592 #[test]
593 fn test_unique_together_validator_new() {
594 let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"]);
595 assert_eq!(validator.field_names().len(), 2);
596 assert_eq!(validator.field_names()[0], "username");
597 assert_eq!(validator.field_names()[1], "email");
598 }
599
600 #[test]
601 fn test_unique_together_validator_with_message() {
602 let validator = UniqueTogetherValidator::<TestUser>::new(vec!["username", "email"])
603 .with_message("Custom combination message");
604 assert_eq!(validator.field_names().len(), 2);
605 assert!(validator.message.is_some());
606 }
607}