1use crate::v2::context::ValidationContext;
4use crate::v2::error::{RuleError, ValidationErrors};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8
9pub trait Validate {
40 fn validate(&self) -> Result<(), ValidationErrors>;
44
45 fn validated(self) -> Result<Self, ValidationErrors>
47 where
48 Self: Sized,
49 {
50 self.validate()?;
51 Ok(self)
52 }
53}
54
55#[async_trait]
86pub trait AsyncValidate: Validate + Send + Sync {
87 async fn validate_async(&self, ctx: &ValidationContext) -> Result<(), ValidationErrors>;
92
93 async fn validate_full(&self, ctx: &ValidationContext) -> Result<(), ValidationErrors> {
95 self.validate()?;
97 self.validate_async(ctx).await
99 }
100
101 async fn validated_async(self, ctx: &ValidationContext) -> Result<Self, ValidationErrors>
103 where
104 Self: Sized,
105 {
106 self.validate_full(ctx).await?;
107 Ok(self)
108 }
109}
110
111pub trait ValidationRule<T: ?Sized>: Debug + Send + Sync {
138 fn validate(&self, value: &T) -> Result<(), RuleError>;
140
141 fn rule_name(&self) -> &'static str;
143
144 fn default_message(&self) -> String {
146 format!("Validation failed for rule '{}'", self.rule_name())
147 }
148}
149
150#[async_trait]
154pub trait AsyncValidationRule<T: ?Sized + Sync>: Debug + Send + Sync {
155 async fn validate_async(&self, value: &T, ctx: &ValidationContext) -> Result<(), RuleError>;
157
158 fn rule_name(&self) -> &'static str;
160
161 fn default_message(&self) -> String {
163 format!("Async validation failed for rule '{}'", self.rule_name())
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172#[serde(tag = "type", rename_all = "snake_case")]
173pub enum SerializableRule {
174 Email {
176 #[serde(skip_serializing_if = "Option::is_none")]
177 message: Option<String>,
178 },
179 Length {
181 #[serde(skip_serializing_if = "Option::is_none")]
182 min: Option<usize>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 max: Option<usize>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 message: Option<String>,
187 },
188 Range {
190 #[serde(skip_serializing_if = "Option::is_none")]
191 min: Option<f64>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 max: Option<f64>,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 message: Option<String>,
196 },
197 Regex {
199 pattern: String,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 message: Option<String>,
202 },
203 Url {
205 #[serde(skip_serializing_if = "Option::is_none")]
206 message: Option<String>,
207 },
208 Required {
210 #[serde(skip_serializing_if = "Option::is_none")]
211 message: Option<String>,
212 },
213 AsyncUnique {
215 table: String,
216 column: String,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 message: Option<String>,
219 },
220 AsyncExists {
222 table: String,
223 column: String,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 message: Option<String>,
226 },
227 AsyncApi {
229 endpoint: String,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 message: Option<String>,
232 },
233}
234
235impl SerializableRule {
236 pub fn pretty_print(&self) -> String {
238 match self {
239 SerializableRule::Email { message } => {
240 let msg = message
241 .as_ref()
242 .map(|m| format!(", message = \"{}\"", m))
243 .unwrap_or_default();
244 format!("#[validate(email{})]", msg)
245 }
246 SerializableRule::Length { min, max, message } => {
247 let mut parts = Vec::new();
248 if let Some(min) = min {
249 parts.push(format!("min = {}", min));
250 }
251 if let Some(max) = max {
252 parts.push(format!("max = {}", max));
253 }
254 if let Some(msg) = message {
255 parts.push(format!("message = \"{}\"", msg));
256 }
257 format!("#[validate(length({}))]", parts.join(", "))
258 }
259 SerializableRule::Range { min, max, message } => {
260 let mut parts = Vec::new();
261 if let Some(min) = min {
262 parts.push(format!("min = {}", min));
263 }
264 if let Some(max) = max {
265 parts.push(format!("max = {}", max));
266 }
267 if let Some(msg) = message {
268 parts.push(format!("message = \"{}\"", msg));
269 }
270 format!("#[validate(range({}))]", parts.join(", "))
271 }
272 SerializableRule::Regex { pattern, message } => {
273 let msg = message
274 .as_ref()
275 .map(|m| format!(", message = \"{}\"", m))
276 .unwrap_or_default();
277 format!("#[validate(regex = \"{}\"{})]", pattern, msg)
278 }
279 SerializableRule::Url { message } => {
280 let msg = message
281 .as_ref()
282 .map(|m| format!(", message = \"{}\"", m))
283 .unwrap_or_default();
284 format!("#[validate(url{})]", msg)
285 }
286 SerializableRule::Required { message } => {
287 let msg = message
288 .as_ref()
289 .map(|m| format!(", message = \"{}\"", m))
290 .unwrap_or_default();
291 format!("#[validate(required{})]", msg)
292 }
293 SerializableRule::AsyncUnique {
294 table,
295 column,
296 message,
297 } => {
298 let msg = message
299 .as_ref()
300 .map(|m| format!(", message = \"{}\"", m))
301 .unwrap_or_default();
302 format!(
303 "#[validate(async_unique(table = \"{}\", column = \"{}\"{}))]",
304 table, column, msg
305 )
306 }
307 SerializableRule::AsyncExists {
308 table,
309 column,
310 message,
311 } => {
312 let msg = message
313 .as_ref()
314 .map(|m| format!(", message = \"{}\"", m))
315 .unwrap_or_default();
316 format!(
317 "#[validate(async_exists(table = \"{}\", column = \"{}\"{}))]",
318 table, column, msg
319 )
320 }
321 SerializableRule::AsyncApi { endpoint, message } => {
322 let msg = message
323 .as_ref()
324 .map(|m| format!(", message = \"{}\"", m))
325 .unwrap_or_default();
326 format!("#[validate(async_api(endpoint = \"{}\"{}))]", endpoint, msg)
327 }
328 }
329 }
330
331 pub fn parse(s: &str) -> Option<Self> {
336 let s = s.trim();
337
338 if !s.starts_with("#[validate(") || !s.ends_with(")]") {
340 return None;
341 }
342
343 let inner = &s[11..s.len() - 2];
345
346 if inner == "email" || inner.starts_with("email,") {
348 let message = Self::extract_message(inner);
349 return Some(SerializableRule::Email { message });
350 }
351
352 if inner == "url" || inner.starts_with("url,") {
353 let message = Self::extract_message(inner);
354 return Some(SerializableRule::Url { message });
355 }
356
357 if inner == "required" || inner.starts_with("required,") {
358 let message = Self::extract_message(inner);
359 return Some(SerializableRule::Required { message });
360 }
361
362 if inner.starts_with("length(") {
363 return Self::parse_length(inner);
364 }
365
366 if inner.starts_with("range(") {
367 return Self::parse_range(inner);
368 }
369
370 if inner.starts_with("regex") {
371 return Self::parse_regex(inner);
372 }
373
374 if inner.starts_with("async_unique(") {
375 return Self::parse_async_unique(inner);
376 }
377
378 if inner.starts_with("async_exists(") {
379 return Self::parse_async_exists(inner);
380 }
381
382 if inner.starts_with("async_api(") {
383 return Self::parse_async_api(inner);
384 }
385
386 None
387 }
388
389 fn extract_message(s: &str) -> Option<String> {
390 if let Some(idx) = s.find("message = \"") {
391 let start = idx + 11;
392 if let Some(end) = s[start..].find('"') {
393 return Some(s[start..start + end].to_string());
394 }
395 }
396 None
397 }
398
399 fn extract_param(s: &str, param: &str) -> Option<String> {
400 let pattern = format!("{} = ", param);
401 if let Some(idx) = s.find(&pattern) {
402 let start = idx + pattern.len();
403 let rest = &s[start..];
404
405 if let Some(stripped) = rest.strip_prefix('"') {
407 if let Some(end) = stripped.find('"') {
408 return Some(stripped[..end].to_string());
409 }
410 } else {
411 let end = rest.find([',', ')']).unwrap_or(rest.len());
413 return Some(rest[..end].trim().to_string());
414 }
415 }
416 None
417 }
418
419 fn parse_length(s: &str) -> Option<Self> {
420 let min = Self::extract_param(s, "min").and_then(|v| v.parse().ok());
421 let max = Self::extract_param(s, "max").and_then(|v| v.parse().ok());
422 let message = Self::extract_message(s);
423 Some(SerializableRule::Length { min, max, message })
424 }
425
426 fn parse_range(s: &str) -> Option<Self> {
427 let min = Self::extract_param(s, "min").and_then(|v| v.parse().ok());
428 let max = Self::extract_param(s, "max").and_then(|v| v.parse().ok());
429 let message = Self::extract_message(s);
430 Some(SerializableRule::Range { min, max, message })
431 }
432
433 fn parse_regex(s: &str) -> Option<Self> {
434 let pattern =
435 Self::extract_param(s, "regex").or_else(|| Self::extract_param(s, "pattern"))?;
436 let message = Self::extract_message(s);
437 Some(SerializableRule::Regex { pattern, message })
438 }
439
440 fn parse_async_unique(s: &str) -> Option<Self> {
441 let table = Self::extract_param(s, "table")?;
442 let column = Self::extract_param(s, "column")?;
443 let message = Self::extract_message(s);
444 Some(SerializableRule::AsyncUnique {
445 table,
446 column,
447 message,
448 })
449 }
450
451 fn parse_async_exists(s: &str) -> Option<Self> {
452 let table = Self::extract_param(s, "table")?;
453 let column = Self::extract_param(s, "column")?;
454 let message = Self::extract_message(s);
455 Some(SerializableRule::AsyncExists {
456 table,
457 column,
458 message,
459 })
460 }
461
462 fn parse_async_api(s: &str) -> Option<Self> {
463 let endpoint = Self::extract_param(s, "endpoint")?;
464 let message = Self::extract_message(s);
465 Some(SerializableRule::AsyncApi { endpoint, message })
466 }
467}
468
469use crate::v2::rules::{
471 AsyncApiRule, AsyncExistsRule, AsyncUniqueRule, EmailRule, LengthRule, RegexRule, RequiredRule,
472 UrlRule,
473};
474
475impl From<EmailRule> for SerializableRule {
476 fn from(rule: EmailRule) -> Self {
477 SerializableRule::Email {
478 message: rule.message,
479 }
480 }
481}
482
483impl From<LengthRule> for SerializableRule {
484 fn from(rule: LengthRule) -> Self {
485 SerializableRule::Length {
486 min: rule.min,
487 max: rule.max,
488 message: rule.message,
489 }
490 }
491}
492
493impl From<RegexRule> for SerializableRule {
494 fn from(rule: RegexRule) -> Self {
495 SerializableRule::Regex {
496 pattern: rule.pattern,
497 message: rule.message,
498 }
499 }
500}
501
502impl From<UrlRule> for SerializableRule {
503 fn from(rule: UrlRule) -> Self {
504 SerializableRule::Url {
505 message: rule.message,
506 }
507 }
508}
509
510impl From<RequiredRule> for SerializableRule {
511 fn from(rule: RequiredRule) -> Self {
512 SerializableRule::Required {
513 message: rule.message,
514 }
515 }
516}
517
518impl From<AsyncUniqueRule> for SerializableRule {
519 fn from(rule: AsyncUniqueRule) -> Self {
520 SerializableRule::AsyncUnique {
521 table: rule.table,
522 column: rule.column,
523 message: rule.message,
524 }
525 }
526}
527
528impl From<AsyncExistsRule> for SerializableRule {
529 fn from(rule: AsyncExistsRule) -> Self {
530 SerializableRule::AsyncExists {
531 table: rule.table,
532 column: rule.column,
533 message: rule.message,
534 }
535 }
536}
537
538impl From<AsyncApiRule> for SerializableRule {
539 fn from(rule: AsyncApiRule) -> Self {
540 SerializableRule::AsyncApi {
541 endpoint: rule.endpoint,
542 message: rule.message,
543 }
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 #[test]
552 fn serializable_rule_email_pretty_print() {
553 let rule = SerializableRule::Email { message: None };
554 assert_eq!(rule.pretty_print(), "#[validate(email)]");
555
556 let rule = SerializableRule::Email {
557 message: Some("Invalid email".to_string()),
558 };
559 assert_eq!(
560 rule.pretty_print(),
561 "#[validate(email, message = \"Invalid email\")]"
562 );
563 }
564
565 #[test]
566 fn serializable_rule_length_pretty_print() {
567 let rule = SerializableRule::Length {
568 min: Some(3),
569 max: Some(50),
570 message: None,
571 };
572 assert_eq!(
573 rule.pretty_print(),
574 "#[validate(length(min = 3, max = 50))]"
575 );
576 }
577
578 #[test]
579 fn serializable_rule_roundtrip() {
580 let rule = SerializableRule::Range {
581 min: Some(18.0),
582 max: Some(120.0),
583 message: Some("Age must be between 18 and 120".to_string()),
584 };
585
586 let json = serde_json::to_string(&rule).unwrap();
587 let parsed: SerializableRule = serde_json::from_str(&json).unwrap();
588 assert_eq!(rule, parsed);
589 }
590
591 #[test]
592 fn serializable_rule_pretty_print_roundtrip_email() {
593 let rule = SerializableRule::Email { message: None };
594 let pretty = rule.pretty_print();
595 let parsed = SerializableRule::parse(&pretty).unwrap();
596 assert_eq!(rule, parsed);
597
598 let rule = SerializableRule::Email {
599 message: Some("Invalid email".to_string()),
600 };
601 let pretty = rule.pretty_print();
602 let parsed = SerializableRule::parse(&pretty).unwrap();
603 assert_eq!(rule, parsed);
604 }
605
606 #[test]
607 fn serializable_rule_pretty_print_roundtrip_length() {
608 let rule = SerializableRule::Length {
609 min: Some(3),
610 max: Some(50),
611 message: None,
612 };
613 let pretty = rule.pretty_print();
614 let parsed = SerializableRule::parse(&pretty).unwrap();
615 assert_eq!(rule, parsed);
616 }
617
618 #[test]
619 fn serializable_rule_pretty_print_roundtrip_range() {
620 let rule = SerializableRule::Range {
621 min: Some(18.0),
622 max: Some(120.0),
623 message: None,
624 };
625 let pretty = rule.pretty_print();
626 let parsed = SerializableRule::parse(&pretty).unwrap();
627 assert_eq!(rule, parsed);
628 }
629
630 #[test]
631 fn serializable_rule_pretty_print_roundtrip_url() {
632 let rule = SerializableRule::Url { message: None };
633 let pretty = rule.pretty_print();
634 let parsed = SerializableRule::parse(&pretty).unwrap();
635 assert_eq!(rule, parsed);
636 }
637
638 #[test]
639 fn serializable_rule_pretty_print_roundtrip_required() {
640 let rule = SerializableRule::Required { message: None };
641 let pretty = rule.pretty_print();
642 let parsed = SerializableRule::parse(&pretty).unwrap();
643 assert_eq!(rule, parsed);
644 }
645
646 #[test]
647 fn serializable_rule_pretty_print_roundtrip_async_unique() {
648 let rule = SerializableRule::AsyncUnique {
649 table: "users".to_string(),
650 column: "email".to_string(),
651 message: None,
652 };
653 let pretty = rule.pretty_print();
654 let parsed = SerializableRule::parse(&pretty).unwrap();
655 assert_eq!(rule, parsed);
656 }
657
658 #[test]
659 fn serializable_rule_pretty_print_roundtrip_async_exists() {
660 let rule = SerializableRule::AsyncExists {
661 table: "categories".to_string(),
662 column: "id".to_string(),
663 message: Some("Category not found".to_string()),
664 };
665 let pretty = rule.pretty_print();
666 let parsed = SerializableRule::parse(&pretty).unwrap();
667 assert_eq!(rule, parsed);
668 }
669
670 #[test]
671 fn serializable_rule_pretty_print_roundtrip_async_api() {
672 let rule = SerializableRule::AsyncApi {
673 endpoint: "https://api.example.com/validate".to_string(),
674 message: None,
675 };
676 let pretty = rule.pretty_print();
677 let parsed = SerializableRule::parse(&pretty).unwrap();
678 assert_eq!(rule, parsed);
679 }
680
681 #[test]
682 fn from_email_rule() {
683 let rule = EmailRule::with_message("Invalid email");
684 let serializable: SerializableRule = rule.into();
685 assert_eq!(
686 serializable,
687 SerializableRule::Email {
688 message: Some("Invalid email".to_string())
689 }
690 );
691 }
692
693 #[test]
694 fn from_length_rule() {
695 let rule = LengthRule::new(3, 50).with_message("Invalid length");
696 let serializable: SerializableRule = rule.into();
697 assert_eq!(
698 serializable,
699 SerializableRule::Length {
700 min: Some(3),
701 max: Some(50),
702 message: Some("Invalid length".to_string())
703 }
704 );
705 }
706
707 #[test]
708 fn from_async_unique_rule() {
709 let rule = AsyncUniqueRule::new("users", "email").with_message("Email taken");
710 let serializable: SerializableRule = rule.into();
711 assert_eq!(
712 serializable,
713 SerializableRule::AsyncUnique {
714 table: "users".to_string(),
715 column: "email".to_string(),
716 message: Some("Email taken".to_string())
717 }
718 );
719 }
720}