prax_schema/ast/
validation.rs

1//! Input validation system for Prax schemas.
2//!
3//! This module provides a robust validation framework that integrates with
4//! documentation comments and field attributes to define validation rules.
5//!
6//! # Validation Syntax
7//!
8//! Validation rules can be specified using the `@validate` attribute or
9//! through structured documentation comments with `@validate:` directives.
10//!
11//! ## Attribute-based Validation
12//!
13//! ```prax
14//! model User {
15//!     id       Int    @id @auto
16//!     email    String @validate.email @validate.maxLength(255)
17//!     username String @validate.minLength(3) @validate.maxLength(30) @validate.regex("^[a-z0-9_]+$")
18//!     age      Int?   @validate.range(0, 150)
19//!     website  String? @validate.url
20//! }
21//! ```
22//!
23//! ## Documentation-based Validation
24//!
25//! ```prax
26//! model User {
27//!     /// The user's email address
28//!     /// @validate: email, maxLength(255)
29//!     email String
30//!
31//!     /// Username must be lowercase alphanumeric with underscores
32//!     /// @validate: minLength(3), maxLength(30)
33//!     /// @validate: regex("^[a-z0-9_]+$")
34//!     username String
35//! }
36//! ```
37//!
38//! # Built-in Validators
39//!
40//! ## String Validators
41//! - `email` - Valid email format (RFC 5322)
42//! - `url` - Valid URL format
43//! - `uuid` - Valid UUID format (v1-v5)
44//! - `cuid` - Valid CUID format
45//! - `regex(pattern)` - Matches regex pattern
46//! - `minLength(n)` - Minimum string length
47//! - `maxLength(n)` - Maximum string length
48//! - `length(min, max)` - String length range
49//! - `startsWith(prefix)` - String starts with prefix
50//! - `endsWith(suffix)` - String ends with suffix
51//! - `contains(substring)` - String contains substring
52//! - `alpha` - Only alphabetic characters
53//! - `alphanumeric` - Only alphanumeric characters
54//! - `lowercase` - Only lowercase characters
55//! - `uppercase` - Only uppercase characters
56//! - `trim` - Trimmed (no leading/trailing whitespace)
57//! - `noWhitespace` - No whitespace characters
58//! - `ip` - Valid IP address (v4 or v6)
59//! - `ipv4` - Valid IPv4 address
60//! - `ipv6` - Valid IPv6 address
61//! - `creditCard` - Valid credit card number (Luhn algorithm)
62//! - `phone` - Valid phone number format
63//! - `slug` - URL-safe slug format
64//! - `hex` - Valid hexadecimal string
65//! - `base64` - Valid base64 string
66//! - `json` - Valid JSON string
67//!
68//! ## Numeric Validators
69//! - `min(n)` - Minimum value
70//! - `max(n)` - Maximum value
71//! - `range(min, max)` - Value within range (inclusive)
72//! - `positive` - Value > 0
73//! - `negative` - Value < 0
74//! - `nonNegative` - Value >= 0
75//! - `nonPositive` - Value <= 0
76//! - `integer` - Must be integer (no decimal)
77//! - `multipleOf(n)` - Value is multiple of n
78//! - `finite` - Must be finite (not Infinity/NaN)
79//!
80//! ## Array Validators
81//! - `minItems(n)` - Minimum array length
82//! - `maxItems(n)` - Maximum array length
83//! - `items(min, max)` - Array length range
84//! - `unique` - All items must be unique
85//! - `nonEmpty` - Array must have at least one item
86//!
87//! ## Date/Time Validators
88//! - `past` - Date must be in the past
89//! - `future` - Date must be in the future
90//! - `pastOrPresent` - Date must be past or present
91//! - `futureOrPresent` - Date must be future or present
92//! - `after(date)` - Date must be after specified date
93//! - `before(date)` - Date must be before specified date
94//!
95//! ## General Validators
96//! - `required` - Field must not be null (for optional fields)
97//! - `notEmpty` - Field must not be empty (string, array, etc.)
98//! - `oneOf(values...)` - Value must be one of the specified values
99//! - `custom(name)` - Use a custom validator function
100//!
101//! # Custom Validators
102//!
103//! Custom validators can be registered and referenced by name:
104//!
105//! ```rust,ignore
106//! // Register custom validator
107//! validation::register_custom("strongPassword", |value: &str| {
108//!     // Must have uppercase, lowercase, number, and special char
109//!     let has_upper = value.chars().any(|c| c.is_uppercase());
110//!     let has_lower = value.chars().any(|c| c.is_lowercase());
111//!     let has_digit = value.chars().any(|c| c.is_numeric());
112//!     let has_special = value.chars().any(|c| !c.is_alphanumeric());
113//!     has_upper && has_lower && has_digit && has_special
114//! });
115//!
116//! // Use in schema
117//! model User {
118//!     password String @validate.custom("strongPassword")
119//! }
120//! ```
121
122use serde::{Deserialize, Serialize};
123use smol_str::SmolStr;
124
125use super::Span;
126
127/// A validation rule for a field.
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct ValidationRule {
130    /// The type of validation.
131    pub rule_type: ValidationType,
132    /// Optional custom error message.
133    pub message: Option<String>,
134    /// Source location.
135    pub span: Span,
136}
137
138impl ValidationRule {
139    /// Create a new validation rule.
140    pub fn new(rule_type: ValidationType, span: Span) -> Self {
141        Self {
142            rule_type,
143            message: None,
144            span,
145        }
146    }
147
148    /// Create a validation rule with a custom message.
149    pub fn with_message(mut self, message: impl Into<String>) -> Self {
150        self.message = Some(message.into());
151        self
152    }
153
154    /// Get the error message for this rule.
155    pub fn error_message(&self, field_name: &str) -> String {
156        if let Some(msg) = &self.message {
157            msg.clone()
158        } else {
159            self.rule_type.default_message(field_name)
160        }
161    }
162
163    /// Check if this rule applies to strings.
164    pub fn is_string_rule(&self) -> bool {
165        self.rule_type.is_string_rule()
166    }
167
168    /// Check if this rule applies to numbers.
169    pub fn is_numeric_rule(&self) -> bool {
170        self.rule_type.is_numeric_rule()
171    }
172
173    /// Check if this rule applies to arrays.
174    pub fn is_array_rule(&self) -> bool {
175        self.rule_type.is_array_rule()
176    }
177
178    /// Check if this rule applies to dates.
179    pub fn is_date_rule(&self) -> bool {
180        self.rule_type.is_date_rule()
181    }
182}
183
184/// Types of validation rules.
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub enum ValidationType {
187    // ==================== String Validators ====================
188    /// Valid email format.
189    Email,
190    /// Valid URL format.
191    Url,
192    /// Valid UUID format (v1-v5).
193    Uuid,
194    /// Valid CUID format.
195    Cuid,
196    /// Valid CUID2 format.
197    Cuid2,
198    /// Valid NanoId format.
199    NanoId,
200    /// Valid ULID format.
201    Ulid,
202    /// Matches regex pattern.
203    Regex(String),
204    /// Minimum string length.
205    MinLength(usize),
206    /// Maximum string length.
207    MaxLength(usize),
208    /// String length range.
209    Length { min: usize, max: usize },
210    /// String starts with prefix.
211    StartsWith(String),
212    /// String ends with suffix.
213    EndsWith(String),
214    /// String contains substring.
215    Contains(String),
216    /// Only alphabetic characters.
217    Alpha,
218    /// Only alphanumeric characters.
219    Alphanumeric,
220    /// Only lowercase characters.
221    Lowercase,
222    /// Only uppercase characters.
223    Uppercase,
224    /// Trimmed (no leading/trailing whitespace).
225    Trim,
226    /// No whitespace characters.
227    NoWhitespace,
228    /// Valid IP address (v4 or v6).
229    Ip,
230    /// Valid IPv4 address.
231    Ipv4,
232    /// Valid IPv6 address.
233    Ipv6,
234    /// Valid credit card number.
235    CreditCard,
236    /// Valid phone number format.
237    Phone,
238    /// URL-safe slug format.
239    Slug,
240    /// Valid hexadecimal string.
241    Hex,
242    /// Valid base64 string.
243    Base64,
244    /// Valid JSON string.
245    Json,
246
247    // ==================== Numeric Validators ====================
248    /// Minimum value.
249    Min(f64),
250    /// Maximum value.
251    Max(f64),
252    /// Value within range (inclusive).
253    Range { min: f64, max: f64 },
254    /// Value > 0.
255    Positive,
256    /// Value < 0.
257    Negative,
258    /// Value >= 0.
259    NonNegative,
260    /// Value <= 0.
261    NonPositive,
262    /// Must be integer (no decimal).
263    Integer,
264    /// Value is multiple of n.
265    MultipleOf(f64),
266    /// Must be finite (not Infinity/NaN).
267    Finite,
268
269    // ==================== Array Validators ====================
270    /// Minimum array length.
271    MinItems(usize),
272    /// Maximum array length.
273    MaxItems(usize),
274    /// Array length range.
275    Items { min: usize, max: usize },
276    /// All items must be unique.
277    Unique,
278    /// Array must have at least one item.
279    NonEmpty,
280
281    // ==================== Date/Time Validators ====================
282    /// Date must be in the past.
283    Past,
284    /// Date must be in the future.
285    Future,
286    /// Date must be past or present.
287    PastOrPresent,
288    /// Date must be future or present.
289    FutureOrPresent,
290    /// Date must be after specified date.
291    After(String),
292    /// Date must be before specified date.
293    Before(String),
294
295    // ==================== General Validators ====================
296    /// Field must not be null (for optional fields).
297    Required,
298    /// Field must not be empty.
299    NotEmpty,
300    /// Value must be one of the specified values.
301    OneOf(Vec<ValidationValue>),
302    /// Use a custom validator function.
303    Custom(String),
304}
305
306impl ValidationType {
307    /// Get the default error message for this validation type.
308    pub fn default_message(&self, field_name: &str) -> String {
309        match self {
310            // String validators
311            Self::Email => format!("{} must be a valid email address", field_name),
312            Self::Url => format!("{} must be a valid URL", field_name),
313            Self::Uuid => format!("{} must be a valid UUID", field_name),
314            Self::Cuid => format!("{} must be a valid CUID", field_name),
315            Self::Cuid2 => format!("{} must be a valid CUID2", field_name),
316            Self::NanoId => format!("{} must be a valid NanoId", field_name),
317            Self::Ulid => format!("{} must be a valid ULID", field_name),
318            Self::Regex(pattern) => format!("{} must match pattern: {}", field_name, pattern),
319            Self::MinLength(n) => format!("{} must be at least {} characters", field_name, n),
320            Self::MaxLength(n) => format!("{} must be at most {} characters", field_name, n),
321            Self::Length { min, max } => {
322                format!(
323                    "{} must be between {} and {} characters",
324                    field_name, min, max
325                )
326            }
327            Self::StartsWith(s) => format!("{} must start with '{}'", field_name, s),
328            Self::EndsWith(s) => format!("{} must end with '{}'", field_name, s),
329            Self::Contains(s) => format!("{} must contain '{}'", field_name, s),
330            Self::Alpha => format!("{} must contain only letters", field_name),
331            Self::Alphanumeric => format!("{} must contain only letters and numbers", field_name),
332            Self::Lowercase => format!("{} must be lowercase", field_name),
333            Self::Uppercase => format!("{} must be uppercase", field_name),
334            Self::Trim => format!(
335                "{} must not have leading or trailing whitespace",
336                field_name
337            ),
338            Self::NoWhitespace => format!("{} must not contain whitespace", field_name),
339            Self::Ip => format!("{} must be a valid IP address", field_name),
340            Self::Ipv4 => format!("{} must be a valid IPv4 address", field_name),
341            Self::Ipv6 => format!("{} must be a valid IPv6 address", field_name),
342            Self::CreditCard => format!("{} must be a valid credit card number", field_name),
343            Self::Phone => format!("{} must be a valid phone number", field_name),
344            Self::Slug => format!("{} must be a valid URL slug", field_name),
345            Self::Hex => format!("{} must be a valid hexadecimal string", field_name),
346            Self::Base64 => format!("{} must be a valid base64 string", field_name),
347            Self::Json => format!("{} must be valid JSON", field_name),
348
349            // Numeric validators
350            Self::Min(n) => format!("{} must be at least {}", field_name, n),
351            Self::Max(n) => format!("{} must be at most {}", field_name, n),
352            Self::Range { min, max } => {
353                format!("{} must be between {} and {}", field_name, min, max)
354            }
355            Self::Positive => format!("{} must be positive", field_name),
356            Self::Negative => format!("{} must be negative", field_name),
357            Self::NonNegative => format!("{} must not be negative", field_name),
358            Self::NonPositive => format!("{} must not be positive", field_name),
359            Self::Integer => format!("{} must be an integer", field_name),
360            Self::MultipleOf(n) => format!("{} must be a multiple of {}", field_name, n),
361            Self::Finite => format!("{} must be a finite number", field_name),
362
363            // Array validators
364            Self::MinItems(n) => format!("{} must have at least {} items", field_name, n),
365            Self::MaxItems(n) => format!("{} must have at most {} items", field_name, n),
366            Self::Items { min, max } => {
367                format!("{} must have between {} and {} items", field_name, min, max)
368            }
369            Self::Unique => format!("{} must have unique items", field_name),
370            Self::NonEmpty => format!("{} must not be empty", field_name),
371
372            // Date validators
373            Self::Past => format!("{} must be in the past", field_name),
374            Self::Future => format!("{} must be in the future", field_name),
375            Self::PastOrPresent => format!("{} must not be in the future", field_name),
376            Self::FutureOrPresent => format!("{} must not be in the past", field_name),
377            Self::After(date) => format!("{} must be after {}", field_name, date),
378            Self::Before(date) => format!("{} must be before {}", field_name, date),
379
380            // General validators
381            Self::Required => format!("{} is required", field_name),
382            Self::NotEmpty => format!("{} must not be empty", field_name),
383            Self::OneOf(values) => {
384                let options: Vec<String> = values.iter().map(|v| v.to_string()).collect();
385                format!("{} must be one of: {}", field_name, options.join(", "))
386            }
387            Self::Custom(name) => format!("{} failed custom validation: {}", field_name, name),
388        }
389    }
390
391    /// Check if this rule applies to strings.
392    pub fn is_string_rule(&self) -> bool {
393        matches!(
394            self,
395            Self::Email
396                | Self::Url
397                | Self::Uuid
398                | Self::Cuid
399                | Self::Cuid2
400                | Self::NanoId
401                | Self::Ulid
402                | Self::Regex(_)
403                | Self::MinLength(_)
404                | Self::MaxLength(_)
405                | Self::Length { .. }
406                | Self::StartsWith(_)
407                | Self::EndsWith(_)
408                | Self::Contains(_)
409                | Self::Alpha
410                | Self::Alphanumeric
411                | Self::Lowercase
412                | Self::Uppercase
413                | Self::Trim
414                | Self::NoWhitespace
415                | Self::Ip
416                | Self::Ipv4
417                | Self::Ipv6
418                | Self::CreditCard
419                | Self::Phone
420                | Self::Slug
421                | Self::Hex
422                | Self::Base64
423                | Self::Json
424        )
425    }
426
427    /// Check if this rule validates an identifier format (UUID, CUID, etc.).
428    pub fn is_id_format_rule(&self) -> bool {
429        matches!(
430            self,
431            Self::Uuid | Self::Cuid | Self::Cuid2 | Self::NanoId | Self::Ulid
432        )
433    }
434
435    /// Check if this rule applies to numbers.
436    pub fn is_numeric_rule(&self) -> bool {
437        matches!(
438            self,
439            Self::Min(_)
440                | Self::Max(_)
441                | Self::Range { .. }
442                | Self::Positive
443                | Self::Negative
444                | Self::NonNegative
445                | Self::NonPositive
446                | Self::Integer
447                | Self::MultipleOf(_)
448                | Self::Finite
449        )
450    }
451
452    /// Check if this rule applies to arrays.
453    pub fn is_array_rule(&self) -> bool {
454        matches!(
455            self,
456            Self::MinItems(_)
457                | Self::MaxItems(_)
458                | Self::Items { .. }
459                | Self::Unique
460                | Self::NonEmpty
461        )
462    }
463
464    /// Check if this rule applies to dates.
465    pub fn is_date_rule(&self) -> bool {
466        matches!(
467            self,
468            Self::Past
469                | Self::Future
470                | Self::PastOrPresent
471                | Self::FutureOrPresent
472                | Self::After(_)
473                | Self::Before(_)
474        )
475    }
476
477    /// Get the validator name (for code generation).
478    pub fn validator_name(&self) -> &'static str {
479        match self {
480            Self::Email => "email",
481            Self::Url => "url",
482            Self::Uuid => "uuid",
483            Self::Cuid => "cuid",
484            Self::Cuid2 => "cuid2",
485            Self::NanoId => "nanoid",
486            Self::Ulid => "ulid",
487            Self::Regex(_) => "regex",
488            Self::MinLength(_) => "min_length",
489            Self::MaxLength(_) => "max_length",
490            Self::Length { .. } => "length",
491            Self::StartsWith(_) => "starts_with",
492            Self::EndsWith(_) => "ends_with",
493            Self::Contains(_) => "contains",
494            Self::Alpha => "alpha",
495            Self::Alphanumeric => "alphanumeric",
496            Self::Lowercase => "lowercase",
497            Self::Uppercase => "uppercase",
498            Self::Trim => "trim",
499            Self::NoWhitespace => "no_whitespace",
500            Self::Ip => "ip",
501            Self::Ipv4 => "ipv4",
502            Self::Ipv6 => "ipv6",
503            Self::CreditCard => "credit_card",
504            Self::Phone => "phone",
505            Self::Slug => "slug",
506            Self::Hex => "hex",
507            Self::Base64 => "base64",
508            Self::Json => "json",
509            Self::Min(_) => "min",
510            Self::Max(_) => "max",
511            Self::Range { .. } => "range",
512            Self::Positive => "positive",
513            Self::Negative => "negative",
514            Self::NonNegative => "non_negative",
515            Self::NonPositive => "non_positive",
516            Self::Integer => "integer",
517            Self::MultipleOf(_) => "multiple_of",
518            Self::Finite => "finite",
519            Self::MinItems(_) => "min_items",
520            Self::MaxItems(_) => "max_items",
521            Self::Items { .. } => "items",
522            Self::Unique => "unique",
523            Self::NonEmpty => "non_empty",
524            Self::Past => "past",
525            Self::Future => "future",
526            Self::PastOrPresent => "past_or_present",
527            Self::FutureOrPresent => "future_or_present",
528            Self::After(_) => "after",
529            Self::Before(_) => "before",
530            Self::Required => "required",
531            Self::NotEmpty => "not_empty",
532            Self::OneOf(_) => "one_of",
533            Self::Custom(_) => "custom",
534        }
535    }
536}
537
538/// A value that can be used in validation rules.
539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
540pub enum ValidationValue {
541    /// String value.
542    String(String),
543    /// Integer value.
544    Int(i64),
545    /// Float value.
546    Float(f64),
547    /// Boolean value.
548    Bool(bool),
549}
550
551impl std::fmt::Display for ValidationValue {
552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553        match self {
554            Self::String(s) => write!(f, "\"{}\"", s),
555            Self::Int(i) => write!(f, "{}", i),
556            Self::Float(n) => write!(f, "{}", n),
557            Self::Bool(b) => write!(f, "{}", b),
558        }
559    }
560}
561
562/// A collection of validation rules for a field.
563#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
564pub struct FieldValidation {
565    /// The validation rules.
566    pub rules: Vec<ValidationRule>,
567}
568
569impl FieldValidation {
570    /// Create empty field validation.
571    pub fn new() -> Self {
572        Self { rules: Vec::new() }
573    }
574
575    /// Add a validation rule.
576    pub fn add_rule(&mut self, rule: ValidationRule) {
577        self.rules.push(rule);
578    }
579
580    /// Check if there are any validation rules.
581    pub fn is_empty(&self) -> bool {
582        self.rules.is_empty()
583    }
584
585    /// Get the number of validation rules.
586    pub fn len(&self) -> usize {
587        self.rules.len()
588    }
589
590    /// Check if this field has any string validation rules.
591    pub fn has_string_rules(&self) -> bool {
592        self.rules.iter().any(|r| r.is_string_rule())
593    }
594
595    /// Check if this field has any numeric validation rules.
596    pub fn has_numeric_rules(&self) -> bool {
597        self.rules.iter().any(|r| r.is_numeric_rule())
598    }
599
600    /// Check if this field has any array validation rules.
601    pub fn has_array_rules(&self) -> bool {
602        self.rules.iter().any(|r| r.is_array_rule())
603    }
604
605    /// Check if this field is required.
606    pub fn is_required(&self) -> bool {
607        self.rules
608            .iter()
609            .any(|r| matches!(r.rule_type, ValidationType::Required))
610    }
611}
612
613/// Enhanced documentation with embedded validation directives.
614#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
615pub struct EnhancedDocumentation {
616    /// The main documentation text (cleaned of validation directives).
617    pub text: String,
618    /// Parsed validation rules from documentation.
619    pub validation: FieldValidation,
620    /// Additional metadata tags.
621    pub tags: Vec<DocTag>,
622    /// Source location.
623    pub span: Span,
624}
625
626impl EnhancedDocumentation {
627    /// Create new enhanced documentation.
628    pub fn new(text: impl Into<String>, span: Span) -> Self {
629        Self {
630            text: text.into(),
631            validation: FieldValidation::new(),
632            tags: Vec::new(),
633            span,
634        }
635    }
636
637    /// Parse documentation text and extract validation rules.
638    pub fn parse(raw_text: &str, span: Span) -> Self {
639        let mut text_lines = Vec::new();
640        let mut validation = FieldValidation::new();
641        let mut tags = Vec::new();
642
643        for line in raw_text.lines() {
644            let trimmed = line.trim();
645
646            // Check for @validate: directive
647            if let Some(validate_content) = trimmed.strip_prefix("@validate:") {
648                // Parse validation rules
649                for rule_str in validate_content.split(',') {
650                    if let Some(rule) = parse_validation_rule(rule_str.trim(), span) {
651                        validation.add_rule(rule);
652                    }
653                }
654            }
655            // Check for other tags
656            else if let Some(tag) = parse_doc_tag(trimmed, span) {
657                tags.push(tag);
658            }
659            // Regular documentation line
660            else {
661                text_lines.push(line);
662            }
663        }
664
665        Self {
666            text: text_lines.join("\n").trim().to_string(),
667            validation,
668            tags,
669            span,
670        }
671    }
672
673    /// Check if this documentation has any validation rules.
674    pub fn has_validation(&self) -> bool {
675        !self.validation.is_empty()
676    }
677
678    /// Get all validation rules.
679    pub fn validation_rules(&self) -> &[ValidationRule] {
680        &self.validation.rules
681    }
682
683    /// Get a specific tag by name.
684    pub fn get_tag(&self, name: &str) -> Option<&DocTag> {
685        self.tags.iter().find(|t| t.name == name)
686    }
687
688    /// Get all tags with a specific name.
689    pub fn get_tags(&self, name: &str) -> Vec<&DocTag> {
690        self.tags.iter().filter(|t| t.name == name).collect()
691    }
692
693    /// Check if a specific tag exists.
694    pub fn has_tag(&self, name: &str) -> bool {
695        self.tags.iter().any(|t| t.name == name)
696    }
697
698    /// Extract field metadata from documentation tags.
699    pub fn extract_metadata(&self) -> FieldMetadata {
700        FieldMetadata::from_tags(&self.tags)
701    }
702
703    /// Check if field is marked as hidden.
704    pub fn is_hidden(&self) -> bool {
705        self.has_tag("hidden") || self.has_tag("internal")
706    }
707
708    /// Check if field is marked as deprecated.
709    pub fn is_deprecated(&self) -> bool {
710        self.has_tag("deprecated")
711    }
712
713    /// Get deprecation info if deprecated.
714    pub fn deprecation_info(&self) -> Option<DeprecationInfo> {
715        self.get_tag("deprecated").map(|tag| {
716            let mut info = DeprecationInfo::new(tag.value.clone().unwrap_or_default());
717            if let Some(since_tag) = self.get_tag("since") {
718                info.since = since_tag.value.clone();
719            }
720            info
721        })
722    }
723
724    /// Check if field is marked as sensitive.
725    pub fn is_sensitive(&self) -> bool {
726        self.has_tag("sensitive") || self.has_tag("writeonly")
727    }
728
729    /// Check if field is marked as readonly.
730    pub fn is_readonly(&self) -> bool {
731        self.has_tag("readonly") || self.has_tag("readOnly")
732    }
733
734    /// Check if field is marked as writeonly.
735    pub fn is_writeonly(&self) -> bool {
736        self.has_tag("writeonly") || self.has_tag("writeOnly")
737    }
738
739    /// Get examples from documentation.
740    pub fn examples(&self) -> Vec<&str> {
741        self.tags
742            .iter()
743            .filter(|t| t.name == "example")
744            .filter_map(|t| t.value.as_deref())
745            .collect()
746    }
747
748    /// Get the display label.
749    pub fn label(&self) -> Option<&str> {
750        self.get_tag("label").and_then(|t| t.value.as_deref())
751    }
752
753    /// Get the placeholder text.
754    pub fn placeholder(&self) -> Option<&str> {
755        self.get_tag("placeholder").and_then(|t| t.value.as_deref())
756    }
757
758    /// Get the version since this field was introduced.
759    pub fn since(&self) -> Option<&str> {
760        self.get_tag("since").and_then(|t| t.value.as_deref())
761    }
762
763    /// Get the field group for UI organization.
764    pub fn group(&self) -> Option<&str> {
765        self.get_tag("group").and_then(|t| t.value.as_deref())
766    }
767}
768
769/// A documentation tag (like @example, @deprecated, @see).
770#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
771pub struct DocTag {
772    /// Tag name (without @).
773    pub name: SmolStr,
774    /// Tag value/content.
775    pub value: Option<String>,
776    /// Source location.
777    pub span: Span,
778}
779
780impl DocTag {
781    /// Create a new documentation tag.
782    pub fn new(name: impl Into<SmolStr>, value: Option<String>, span: Span) -> Self {
783        Self {
784            name: name.into(),
785            value,
786            span,
787        }
788    }
789}
790
791// ============================================================================
792// Field Metadata - Documentation & API Behavior
793// ============================================================================
794
795/// Comprehensive metadata for a field controlling visibility, deprecation, and API behavior.
796///
797/// This can be specified via documentation comments or attributes:
798///
799/// ```prax
800/// model User {
801///     /// @hidden
802///     internal_id   String
803///
804///     /// @deprecated Use `newEmail` instead
805///     /// @since 1.0.0
806///     oldEmail      String?
807///
808///     /// @sensitive
809///     /// @writeonly
810///     password      String
811///
812///     /// @readonly
813///     /// @example "2024-01-15T10:30:00Z"
814///     createdAt     DateTime
815///
816///     /// @label "Email Address"
817///     /// @placeholder "user@example.com"
818///     email         String @validate.email
819/// }
820/// ```
821#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
822pub struct FieldMetadata {
823    // ==================== Visibility Controls ====================
824    /// Field is hidden from public API/documentation.
825    pub hidden: bool,
826    /// Field is internal (may be exposed in admin APIs).
827    pub internal: bool,
828    /// Field contains sensitive data (should be masked in logs).
829    pub sensitive: bool,
830
831    // ==================== API Behavior ====================
832    /// Field is read-only (cannot be set via API).
833    pub readonly: bool,
834    /// Field is write-only (not returned in responses, e.g., passwords).
835    pub writeonly: bool,
836    /// Field is only valid for input (create/update).
837    pub input_only: bool,
838    /// Field is only valid for output (responses).
839    pub output_only: bool,
840    /// Omit this field from API responses entirely.
841    pub omit_from_output: bool,
842    /// Omit this field from input schemas.
843    pub omit_from_input: bool,
844
845    // ==================== Deprecation ====================
846    /// Field is deprecated.
847    pub deprecated: Option<DeprecationInfo>,
848
849    // ==================== Documentation ====================
850    /// Human-readable label for the field.
851    pub label: Option<String>,
852    /// Description override (if different from doc comment).
853    pub description: Option<String>,
854    /// Placeholder text for input fields.
855    pub placeholder: Option<String>,
856    /// Example values.
857    pub examples: Vec<String>,
858    /// Related fields or documentation references.
859    pub see_also: Vec<String>,
860    /// Version when the field was introduced.
861    pub since: Option<String>,
862
863    // ==================== Serialization ====================
864    /// Alias name for serialization.
865    pub alias: Option<String>,
866    /// Override the serialized field name.
867    pub serialized_name: Option<String>,
868    /// Field order hint for serialization.
869    pub order: Option<i32>,
870    /// Default value for serialization (if missing).
871    pub default_value: Option<String>,
872
873    // ==================== UI Hints ====================
874    /// Field group/section for UI organization.
875    pub group: Option<String>,
876    /// Display format hint (e.g., "date", "currency", "percent").
877    pub format: Option<String>,
878    /// Input type hint (e.g., "textarea", "select", "radio").
879    pub input_type: Option<String>,
880    /// Maximum display width.
881    pub max_width: Option<u32>,
882    /// Field should be displayed as multiline.
883    pub multiline: bool,
884    /// Rich text/HTML allowed.
885    pub rich_text: bool,
886}
887
888impl FieldMetadata {
889    /// Create empty field metadata.
890    pub fn new() -> Self {
891        Self::default()
892    }
893
894    /// Check if field should be excluded from public documentation.
895    pub fn is_hidden(&self) -> bool {
896        self.hidden || self.internal
897    }
898
899    /// Check if field should be excluded from API responses.
900    pub fn should_omit_from_output(&self) -> bool {
901        self.omit_from_output || self.writeonly || self.hidden
902    }
903
904    /// Check if field should be excluded from API input.
905    pub fn should_omit_from_input(&self) -> bool {
906        self.omit_from_input || self.readonly || self.output_only
907    }
908
909    /// Check if field is deprecated.
910    pub fn is_deprecated(&self) -> bool {
911        self.deprecated.is_some()
912    }
913
914    /// Get deprecation message if deprecated.
915    pub fn deprecation_message(&self) -> Option<&str> {
916        self.deprecated.as_ref().map(|d| d.message.as_str())
917    }
918
919    /// Check if field contains sensitive data.
920    pub fn is_sensitive(&self) -> bool {
921        self.sensitive || self.writeonly
922    }
923
924    /// Get the display label (or None if not set).
925    pub fn display_label(&self) -> Option<&str> {
926        self.label.as_deref()
927    }
928
929    /// Get all examples.
930    pub fn get_examples(&self) -> &[String] {
931        &self.examples
932    }
933
934    /// Parse metadata from a list of doc tags.
935    pub fn from_tags(tags: &[DocTag]) -> Self {
936        let mut meta = Self::new();
937
938        for tag in tags {
939            match tag.name.as_str() {
940                // Visibility
941                "hidden" => meta.hidden = true,
942                "internal" => meta.internal = true,
943                "sensitive" => meta.sensitive = true,
944
945                // API behavior
946                "readonly" | "readOnly" => meta.readonly = true,
947                "writeonly" | "writeOnly" => meta.writeonly = true,
948                "inputOnly" | "input_only" => meta.input_only = true,
949                "outputOnly" | "output_only" => meta.output_only = true,
950                "omitFromOutput" | "omit_from_output" => meta.omit_from_output = true,
951                "omitFromInput" | "omit_from_input" => meta.omit_from_input = true,
952
953                // Deprecation
954                "deprecated" => {
955                    meta.deprecated = Some(DeprecationInfo {
956                        message: tag.value.clone().unwrap_or_default(),
957                        since: None,
958                        replacement: None,
959                    });
960                }
961
962                // Documentation
963                "label" => meta.label = tag.value.clone(),
964                "description" | "desc" => meta.description = tag.value.clone(),
965                "placeholder" => meta.placeholder = tag.value.clone(),
966                "example" => {
967                    if let Some(val) = &tag.value {
968                        meta.examples.push(val.clone());
969                    }
970                }
971                "see" | "seeAlso" | "see_also" => {
972                    if let Some(val) = &tag.value {
973                        meta.see_also.push(val.clone());
974                    }
975                }
976                "since" => meta.since = tag.value.clone(),
977
978                // Serialization
979                "alias" => meta.alias = tag.value.clone(),
980                "serializedName" | "serialized_name" | "json" => {
981                    meta.serialized_name = tag.value.clone()
982                }
983                "order" => {
984                    if let Some(val) = &tag.value {
985                        meta.order = val.parse().ok();
986                    }
987                }
988                "default" => meta.default_value = tag.value.clone(),
989
990                // UI hints
991                "group" => meta.group = tag.value.clone(),
992                "format" => meta.format = tag.value.clone(),
993                "inputType" | "input_type" => meta.input_type = tag.value.clone(),
994                "maxWidth" | "max_width" => {
995                    if let Some(val) = &tag.value {
996                        meta.max_width = val.parse().ok();
997                    }
998                }
999                "multiline" => meta.multiline = true,
1000                "richText" | "rich_text" | "html" => meta.rich_text = true,
1001
1002                _ => {}
1003            }
1004        }
1005
1006        meta
1007    }
1008}
1009
1010/// Information about a deprecated field.
1011#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1012pub struct DeprecationInfo {
1013    /// Deprecation message/reason.
1014    pub message: String,
1015    /// Version when deprecated.
1016    pub since: Option<String>,
1017    /// Replacement field or method.
1018    pub replacement: Option<String>,
1019}
1020
1021impl DeprecationInfo {
1022    /// Create new deprecation info.
1023    pub fn new(message: impl Into<String>) -> Self {
1024        Self {
1025            message: message.into(),
1026            since: None,
1027            replacement: None,
1028        }
1029    }
1030
1031    /// Set the version when deprecated.
1032    pub fn since(mut self, version: impl Into<String>) -> Self {
1033        self.since = Some(version.into());
1034        self
1035    }
1036
1037    /// Set the replacement field.
1038    pub fn replacement(mut self, field: impl Into<String>) -> Self {
1039        self.replacement = Some(field.into());
1040        self
1041    }
1042
1043    /// Format for display.
1044    pub fn format_message(&self) -> String {
1045        let mut msg = self.message.clone();
1046        if let Some(since) = &self.since {
1047            msg.push_str(&format!(" (since {})", since));
1048        }
1049        if let Some(replacement) = &self.replacement {
1050            msg.push_str(&format!(" Use {} instead.", replacement));
1051        }
1052        msg
1053    }
1054}
1055
1056/// Visibility level for a field or model.
1057#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1058pub enum Visibility {
1059    /// Publicly visible in all APIs and documentation.
1060    #[default]
1061    Public,
1062    /// Internal - visible in admin APIs but not public.
1063    Internal,
1064    /// Hidden - not visible in any generated API.
1065    Hidden,
1066    /// Private - only accessible within the application.
1067    Private,
1068}
1069
1070impl Visibility {
1071    /// Check if visible in public APIs.
1072    pub fn is_public(&self) -> bool {
1073        matches!(self, Self::Public)
1074    }
1075
1076    /// Check if visible in admin APIs.
1077    pub fn is_admin_visible(&self) -> bool {
1078        matches!(self, Self::Public | Self::Internal)
1079    }
1080
1081    /// Parse from string.
1082    pub fn parse(s: &str) -> Option<Self> {
1083        match s.to_lowercase().as_str() {
1084            "public" => Some(Self::Public),
1085            "internal" => Some(Self::Internal),
1086            "hidden" => Some(Self::Hidden),
1087            "private" => Some(Self::Private),
1088            _ => None,
1089        }
1090    }
1091}
1092
1093impl std::fmt::Display for Visibility {
1094    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095        match self {
1096            Self::Public => write!(f, "public"),
1097            Self::Internal => write!(f, "internal"),
1098            Self::Hidden => write!(f, "hidden"),
1099            Self::Private => write!(f, "private"),
1100        }
1101    }
1102}
1103
1104/// API operation permissions for a field.
1105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1106pub struct FieldPermissions {
1107    /// Field can be read.
1108    pub read: bool,
1109    /// Field can be set on create.
1110    pub create: bool,
1111    /// Field can be updated.
1112    pub update: bool,
1113    /// Field can be used in filters.
1114    pub filter: bool,
1115    /// Field can be used in sorting.
1116    pub sort: bool,
1117}
1118
1119impl FieldPermissions {
1120    /// All permissions enabled.
1121    pub fn all() -> Self {
1122        Self {
1123            read: true,
1124            create: true,
1125            update: true,
1126            filter: true,
1127            sort: true,
1128        }
1129    }
1130
1131    /// Read-only permissions.
1132    pub fn readonly() -> Self {
1133        Self {
1134            read: true,
1135            create: false,
1136            update: false,
1137            filter: true,
1138            sort: true,
1139        }
1140    }
1141
1142    /// Write-only permissions (e.g., passwords).
1143    pub fn writeonly() -> Self {
1144        Self {
1145            read: false,
1146            create: true,
1147            update: true,
1148            filter: false,
1149            sort: false,
1150        }
1151    }
1152
1153    /// No permissions (internal field).
1154    pub fn none() -> Self {
1155        Self::default()
1156    }
1157
1158    /// Create from field metadata.
1159    pub fn from_metadata(meta: &FieldMetadata) -> Self {
1160        if meta.hidden {
1161            return Self::none();
1162        }
1163
1164        Self {
1165            read: !meta.writeonly && !meta.omit_from_output,
1166            create: !meta.readonly && !meta.output_only && !meta.omit_from_input,
1167            update: !meta.readonly && !meta.output_only && !meta.omit_from_input,
1168            filter: !meta.writeonly && !meta.sensitive,
1169            sort: !meta.writeonly && !meta.sensitive,
1170        }
1171    }
1172}
1173
1174/// Parse a validation rule from a string.
1175fn parse_validation_rule(s: &str, span: Span) -> Option<ValidationRule> {
1176    let s = s.trim();
1177    if s.is_empty() {
1178        return None;
1179    }
1180
1181    // Check for function-style validators: name(args)
1182    if let Some(paren_idx) = s.find('(') {
1183        let name = &s[..paren_idx];
1184        let args_str = s[paren_idx + 1..].trim_end_matches(')');
1185
1186        let rule_type = match name {
1187            "minLength" | "min_length" => {
1188                let n: usize = args_str.trim().parse().ok()?;
1189                ValidationType::MinLength(n)
1190            }
1191            "maxLength" | "max_length" => {
1192                let n: usize = args_str.trim().parse().ok()?;
1193                ValidationType::MaxLength(n)
1194            }
1195            "length" => {
1196                let parts: Vec<&str> = args_str.split(',').collect();
1197                if parts.len() == 2 {
1198                    let min: usize = parts[0].trim().parse().ok()?;
1199                    let max: usize = parts[1].trim().parse().ok()?;
1200                    ValidationType::Length { min, max }
1201                } else {
1202                    return None;
1203                }
1204            }
1205            "min" => {
1206                let n: f64 = args_str.trim().parse().ok()?;
1207                ValidationType::Min(n)
1208            }
1209            "max" => {
1210                let n: f64 = args_str.trim().parse().ok()?;
1211                ValidationType::Max(n)
1212            }
1213            "range" => {
1214                let parts: Vec<&str> = args_str.split(',').collect();
1215                if parts.len() == 2 {
1216                    let min: f64 = parts[0].trim().parse().ok()?;
1217                    let max: f64 = parts[1].trim().parse().ok()?;
1218                    ValidationType::Range { min, max }
1219                } else {
1220                    return None;
1221                }
1222            }
1223            "regex" => {
1224                let pattern = args_str.trim().trim_matches('"').trim_matches('\'');
1225                ValidationType::Regex(pattern.to_string())
1226            }
1227            "startsWith" | "starts_with" => {
1228                let prefix = args_str.trim().trim_matches('"').trim_matches('\'');
1229                ValidationType::StartsWith(prefix.to_string())
1230            }
1231            "endsWith" | "ends_with" => {
1232                let suffix = args_str.trim().trim_matches('"').trim_matches('\'');
1233                ValidationType::EndsWith(suffix.to_string())
1234            }
1235            "contains" => {
1236                let substring = args_str.trim().trim_matches('"').trim_matches('\'');
1237                ValidationType::Contains(substring.to_string())
1238            }
1239            "minItems" | "min_items" => {
1240                let n: usize = args_str.trim().parse().ok()?;
1241                ValidationType::MinItems(n)
1242            }
1243            "maxItems" | "max_items" => {
1244                let n: usize = args_str.trim().parse().ok()?;
1245                ValidationType::MaxItems(n)
1246            }
1247            "items" => {
1248                let parts: Vec<&str> = args_str.split(',').collect();
1249                if parts.len() == 2 {
1250                    let min: usize = parts[0].trim().parse().ok()?;
1251                    let max: usize = parts[1].trim().parse().ok()?;
1252                    ValidationType::Items { min, max }
1253                } else {
1254                    return None;
1255                }
1256            }
1257            "multipleOf" | "multiple_of" => {
1258                let n: f64 = args_str.trim().parse().ok()?;
1259                ValidationType::MultipleOf(n)
1260            }
1261            "after" => {
1262                let date = args_str.trim().trim_matches('"').trim_matches('\'');
1263                ValidationType::After(date.to_string())
1264            }
1265            "before" => {
1266                let date = args_str.trim().trim_matches('"').trim_matches('\'');
1267                ValidationType::Before(date.to_string())
1268            }
1269            "oneOf" | "one_of" => {
1270                let values = parse_one_of_values(args_str);
1271                ValidationType::OneOf(values)
1272            }
1273            "custom" => {
1274                let name = args_str.trim().trim_matches('"').trim_matches('\'');
1275                ValidationType::Custom(name.to_string())
1276            }
1277            _ => return None,
1278        };
1279
1280        Some(ValidationRule::new(rule_type, span))
1281    } else {
1282        // Simple validators without arguments
1283        let rule_type = match s {
1284            "email" => ValidationType::Email,
1285            "url" => ValidationType::Url,
1286            "uuid" => ValidationType::Uuid,
1287            "cuid" => ValidationType::Cuid,
1288            "cuid2" => ValidationType::Cuid2,
1289            "nanoid" | "nanoId" | "NanoId" => ValidationType::NanoId,
1290            "ulid" => ValidationType::Ulid,
1291            "alpha" => ValidationType::Alpha,
1292            "alphanumeric" => ValidationType::Alphanumeric,
1293            "lowercase" => ValidationType::Lowercase,
1294            "uppercase" => ValidationType::Uppercase,
1295            "trim" => ValidationType::Trim,
1296            "noWhitespace" | "no_whitespace" => ValidationType::NoWhitespace,
1297            "ip" => ValidationType::Ip,
1298            "ipv4" => ValidationType::Ipv4,
1299            "ipv6" => ValidationType::Ipv6,
1300            "creditCard" | "credit_card" => ValidationType::CreditCard,
1301            "phone" => ValidationType::Phone,
1302            "slug" => ValidationType::Slug,
1303            "hex" => ValidationType::Hex,
1304            "base64" => ValidationType::Base64,
1305            "json" => ValidationType::Json,
1306            "positive" => ValidationType::Positive,
1307            "negative" => ValidationType::Negative,
1308            "nonNegative" | "non_negative" => ValidationType::NonNegative,
1309            "nonPositive" | "non_positive" => ValidationType::NonPositive,
1310            "integer" => ValidationType::Integer,
1311            "finite" => ValidationType::Finite,
1312            "unique" => ValidationType::Unique,
1313            "nonEmpty" | "non_empty" => ValidationType::NonEmpty,
1314            "past" => ValidationType::Past,
1315            "future" => ValidationType::Future,
1316            "pastOrPresent" | "past_or_present" => ValidationType::PastOrPresent,
1317            "futureOrPresent" | "future_or_present" => ValidationType::FutureOrPresent,
1318            "required" => ValidationType::Required,
1319            "notEmpty" | "not_empty" => ValidationType::NotEmpty,
1320            _ => return None,
1321        };
1322
1323        Some(ValidationRule::new(rule_type, span))
1324    }
1325}
1326
1327/// Parse values for oneOf validation.
1328fn parse_one_of_values(s: &str) -> Vec<ValidationValue> {
1329    let mut values = Vec::new();
1330
1331    // Simple parsing - split by comma, handling quoted strings
1332    let mut current = String::new();
1333    let mut in_quotes = false;
1334    let mut quote_char = '"';
1335
1336    for c in s.chars() {
1337        match c {
1338            '"' | '\'' if !in_quotes => {
1339                in_quotes = true;
1340                quote_char = c;
1341            }
1342            c if c == quote_char && in_quotes => {
1343                in_quotes = false;
1344            }
1345            ',' if !in_quotes => {
1346                if let Some(val) = parse_validation_value(current.trim()) {
1347                    values.push(val);
1348                }
1349                current.clear();
1350            }
1351            _ => {
1352                current.push(c);
1353            }
1354        }
1355    }
1356
1357    // Don't forget the last value
1358    if let Some(val) = parse_validation_value(current.trim()) {
1359        values.push(val);
1360    }
1361
1362    values
1363}
1364
1365/// Parse a single validation value.
1366fn parse_validation_value(s: &str) -> Option<ValidationValue> {
1367    let s = s.trim();
1368    if s.is_empty() {
1369        return None;
1370    }
1371
1372    // Check for quoted string
1373    if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
1374        let inner = &s[1..s.len() - 1];
1375        return Some(ValidationValue::String(inner.to_string()));
1376    }
1377
1378    // Check for boolean
1379    if s == "true" {
1380        return Some(ValidationValue::Bool(true));
1381    }
1382    if s == "false" {
1383        return Some(ValidationValue::Bool(false));
1384    }
1385
1386    // Check for integer
1387    if let Ok(i) = s.parse::<i64>() {
1388        return Some(ValidationValue::Int(i));
1389    }
1390
1391    // Check for float
1392    if let Ok(f) = s.parse::<f64>() {
1393        return Some(ValidationValue::Float(f));
1394    }
1395
1396    // Default to string
1397    Some(ValidationValue::String(s.to_string()))
1398}
1399
1400/// Parse a documentation tag.
1401fn parse_doc_tag(s: &str, span: Span) -> Option<DocTag> {
1402    if !s.starts_with('@') || s.starts_with("@validate") {
1403        return None;
1404    }
1405
1406    let content = &s[1..]; // Remove @
1407    let (name, value) = if let Some(space_idx) = content.find(char::is_whitespace) {
1408        (
1409            &content[..space_idx],
1410            Some(content[space_idx..].trim().to_string()),
1411        )
1412    } else {
1413        (content, None)
1414    };
1415
1416    Some(DocTag::new(name, value, span))
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421    use super::*;
1422
1423    #[test]
1424    fn test_validation_rule_new() {
1425        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1426        assert!(matches!(rule.rule_type, ValidationType::Email));
1427        assert!(rule.message.is_none());
1428    }
1429
1430    #[test]
1431    fn test_validation_rule_with_message() {
1432        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1433            .with_message("Please enter a valid email");
1434        assert_eq!(rule.message, Some("Please enter a valid email".to_string()));
1435    }
1436
1437    #[test]
1438    fn test_validation_type_default_messages() {
1439        let email_msg = ValidationType::Email.default_message("email");
1440        assert!(email_msg.contains("email"));
1441        assert!(email_msg.contains("valid"));
1442
1443        let min_msg = ValidationType::Min(10.0).default_message("age");
1444        assert!(min_msg.contains("age"));
1445        assert!(min_msg.contains("10"));
1446    }
1447
1448    #[test]
1449    fn test_validation_type_is_string_rule() {
1450        assert!(ValidationType::Email.is_string_rule());
1451        assert!(ValidationType::Regex(".*".to_string()).is_string_rule());
1452        assert!(!ValidationType::Min(0.0).is_string_rule());
1453    }
1454
1455    #[test]
1456    fn test_validation_type_is_numeric_rule() {
1457        assert!(ValidationType::Min(0.0).is_numeric_rule());
1458        assert!(ValidationType::Positive.is_numeric_rule());
1459        assert!(!ValidationType::Email.is_numeric_rule());
1460    }
1461
1462    #[test]
1463    fn test_validation_type_is_array_rule() {
1464        assert!(ValidationType::MinItems(1).is_array_rule());
1465        assert!(ValidationType::Unique.is_array_rule());
1466        assert!(!ValidationType::Email.is_array_rule());
1467    }
1468
1469    #[test]
1470    fn test_validation_type_is_date_rule() {
1471        assert!(ValidationType::Past.is_date_rule());
1472        assert!(ValidationType::After("2024-01-01".to_string()).is_date_rule());
1473        assert!(!ValidationType::Email.is_date_rule());
1474    }
1475
1476    #[test]
1477    fn test_field_validation() {
1478        let mut validation = FieldValidation::new();
1479        assert!(validation.is_empty());
1480
1481        validation.add_rule(ValidationRule::new(ValidationType::Email, Span::new(0, 0)));
1482        validation.add_rule(ValidationRule::new(
1483            ValidationType::MaxLength(255),
1484            Span::new(0, 0),
1485        ));
1486
1487        assert_eq!(validation.len(), 2);
1488        assert!(!validation.is_empty());
1489        assert!(validation.has_string_rules());
1490    }
1491
1492    #[test]
1493    fn test_field_validation_is_required() {
1494        let mut validation = FieldValidation::new();
1495        assert!(!validation.is_required());
1496
1497        validation.add_rule(ValidationRule::new(
1498            ValidationType::Required,
1499            Span::new(0, 0),
1500        ));
1501        assert!(validation.is_required());
1502    }
1503
1504    #[test]
1505    fn test_parse_validation_rule_simple() {
1506        let span = Span::new(0, 0);
1507
1508        let email = parse_validation_rule("email", span).unwrap();
1509        assert!(matches!(email.rule_type, ValidationType::Email));
1510
1511        let uuid = parse_validation_rule("uuid", span).unwrap();
1512        assert!(matches!(uuid.rule_type, ValidationType::Uuid));
1513
1514        let positive = parse_validation_rule("positive", span).unwrap();
1515        assert!(matches!(positive.rule_type, ValidationType::Positive));
1516    }
1517
1518    #[test]
1519    fn test_parse_validation_rule_with_args() {
1520        let span = Span::new(0, 0);
1521
1522        let min_length = parse_validation_rule("minLength(5)", span).unwrap();
1523        assert!(matches!(min_length.rule_type, ValidationType::MinLength(5)));
1524
1525        let max = parse_validation_rule("max(100)", span).unwrap();
1526        assert!(
1527            matches!(max.rule_type, ValidationType::Max(n) if (n - 100.0).abs() < f64::EPSILON)
1528        );
1529
1530        let range = parse_validation_rule("range(0, 100)", span).unwrap();
1531        if let ValidationType::Range { min, max } = range.rule_type {
1532            assert!((min - 0.0).abs() < f64::EPSILON);
1533            assert!((max - 100.0).abs() < f64::EPSILON);
1534        } else {
1535            panic!("Expected Range");
1536        }
1537    }
1538
1539    #[test]
1540    fn test_parse_validation_rule_regex() {
1541        let span = Span::new(0, 0);
1542
1543        let regex = parse_validation_rule(r#"regex("^[a-z]+$")"#, span).unwrap();
1544        if let ValidationType::Regex(pattern) = regex.rule_type {
1545            assert_eq!(pattern, "^[a-z]+$");
1546        } else {
1547            panic!("Expected Regex");
1548        }
1549    }
1550
1551    #[test]
1552    fn test_parse_validation_rule_one_of() {
1553        let span = Span::new(0, 0);
1554
1555        let one_of = parse_validation_rule(r#"oneOf("a", "b", "c")"#, span).unwrap();
1556        if let ValidationType::OneOf(values) = one_of.rule_type {
1557            assert_eq!(values.len(), 3);
1558            assert_eq!(values[0], ValidationValue::String("a".to_string()));
1559        } else {
1560            panic!("Expected OneOf");
1561        }
1562    }
1563
1564    #[test]
1565    fn test_parse_validation_value() {
1566        assert_eq!(
1567            parse_validation_value("\"hello\""),
1568            Some(ValidationValue::String("hello".to_string()))
1569        );
1570        assert_eq!(parse_validation_value("42"), Some(ValidationValue::Int(42)));
1571        assert_eq!(
1572            parse_validation_value("3.14"),
1573            Some(ValidationValue::Float(3.14))
1574        );
1575        assert_eq!(
1576            parse_validation_value("true"),
1577            Some(ValidationValue::Bool(true))
1578        );
1579    }
1580
1581    #[test]
1582    fn test_enhanced_documentation_parse() {
1583        let raw = r#"The user's email address
1584@validate: email, maxLength(255)
1585@deprecated Use newEmail instead"#;
1586
1587        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1588
1589        assert_eq!(doc.text, "The user's email address");
1590        assert!(doc.has_validation());
1591        assert_eq!(doc.validation.len(), 2);
1592        assert_eq!(doc.tags.len(), 1);
1593        assert_eq!(doc.tags[0].name.as_str(), "deprecated");
1594    }
1595
1596    #[test]
1597    fn test_enhanced_documentation_multiple_validate_lines() {
1598        let raw = r#"Username must be valid
1599@validate: minLength(3), maxLength(30)
1600@validate: regex("^[a-z0-9_]+$")"#;
1601
1602        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1603
1604        assert_eq!(doc.text, "Username must be valid");
1605        assert_eq!(doc.validation.len(), 3);
1606    }
1607
1608    #[test]
1609    fn test_doc_tag_parsing() {
1610        let span = Span::new(0, 0);
1611
1612        let tag = parse_doc_tag("@deprecated Use newField instead", span).unwrap();
1613        assert_eq!(tag.name.as_str(), "deprecated");
1614        assert_eq!(tag.value, Some("Use newField instead".to_string()));
1615
1616        let tag_no_value = parse_doc_tag("@internal", span).unwrap();
1617        assert_eq!(tag_no_value.name.as_str(), "internal");
1618        assert!(tag_no_value.value.is_none());
1619    }
1620
1621    #[test]
1622    fn test_validation_value_display() {
1623        assert_eq!(
1624            format!("{}", ValidationValue::String("test".to_string())),
1625            "\"test\""
1626        );
1627        assert_eq!(format!("{}", ValidationValue::Int(42)), "42");
1628        assert_eq!(format!("{}", ValidationValue::Float(3.14)), "3.14");
1629        assert_eq!(format!("{}", ValidationValue::Bool(true)), "true");
1630    }
1631
1632    #[test]
1633    fn test_validator_name() {
1634        assert_eq!(ValidationType::Email.validator_name(), "email");
1635        assert_eq!(ValidationType::MinLength(5).validator_name(), "min_length");
1636        assert_eq!(
1637            ValidationType::Range {
1638                min: 0.0,
1639                max: 100.0
1640            }
1641            .validator_name(),
1642            "range"
1643        );
1644    }
1645
1646    // ==================== Field Metadata Tests ====================
1647
1648    #[test]
1649    fn test_field_metadata_default() {
1650        let meta = FieldMetadata::new();
1651        assert!(!meta.hidden);
1652        assert!(!meta.internal);
1653        assert!(!meta.sensitive);
1654        assert!(!meta.readonly);
1655        assert!(!meta.writeonly);
1656        assert!(meta.deprecated.is_none());
1657        assert!(meta.label.is_none());
1658        assert!(meta.examples.is_empty());
1659    }
1660
1661    #[test]
1662    fn test_field_metadata_from_tags() {
1663        let span = Span::new(0, 0);
1664        let tags = vec![
1665            DocTag::new("hidden", None, span),
1666            DocTag::new("sensitive", None, span),
1667            DocTag::new("label", Some("User ID".to_string()), span),
1668            DocTag::new("example", Some("12345".to_string()), span),
1669            DocTag::new("example", Some("67890".to_string()), span),
1670        ];
1671
1672        let meta = FieldMetadata::from_tags(&tags);
1673
1674        assert!(meta.hidden);
1675        assert!(meta.sensitive);
1676        assert_eq!(meta.label, Some("User ID".to_string()));
1677        assert_eq!(meta.examples.len(), 2);
1678        assert_eq!(meta.examples[0], "12345");
1679        assert_eq!(meta.examples[1], "67890");
1680    }
1681
1682    #[test]
1683    fn test_field_metadata_deprecated() {
1684        let span = Span::new(0, 0);
1685        let tags = vec![DocTag::new(
1686            "deprecated",
1687            Some("Use newField instead".to_string()),
1688            span,
1689        )];
1690
1691        let meta = FieldMetadata::from_tags(&tags);
1692
1693        assert!(meta.is_deprecated());
1694        assert_eq!(meta.deprecation_message(), Some("Use newField instead"));
1695    }
1696
1697    #[test]
1698    fn test_field_metadata_readonly_writeonly() {
1699        let span = Span::new(0, 0);
1700
1701        let readonly_tags = vec![DocTag::new("readonly", None, span)];
1702        let readonly_meta = FieldMetadata::from_tags(&readonly_tags);
1703        assert!(readonly_meta.readonly);
1704        assert!(readonly_meta.should_omit_from_input());
1705        assert!(!readonly_meta.should_omit_from_output());
1706
1707        let writeonly_tags = vec![DocTag::new("writeonly", None, span)];
1708        let writeonly_meta = FieldMetadata::from_tags(&writeonly_tags);
1709        assert!(writeonly_meta.writeonly);
1710        assert!(writeonly_meta.should_omit_from_output());
1711        assert!(!writeonly_meta.should_omit_from_input());
1712    }
1713
1714    #[test]
1715    fn test_field_metadata_serialization() {
1716        let span = Span::new(0, 0);
1717        let tags = vec![
1718            DocTag::new("alias", Some("userId".to_string()), span),
1719            DocTag::new("serializedName", Some("user_id".to_string()), span),
1720            DocTag::new("order", Some("1".to_string()), span),
1721        ];
1722
1723        let meta = FieldMetadata::from_tags(&tags);
1724
1725        assert_eq!(meta.alias, Some("userId".to_string()));
1726        assert_eq!(meta.serialized_name, Some("user_id".to_string()));
1727        assert_eq!(meta.order, Some(1));
1728    }
1729
1730    #[test]
1731    fn test_field_metadata_ui_hints() {
1732        let span = Span::new(0, 0);
1733        let tags = vec![
1734            DocTag::new("group", Some("Personal Info".to_string()), span),
1735            DocTag::new("format", Some("date".to_string()), span),
1736            DocTag::new("inputType", Some("textarea".to_string()), span),
1737            DocTag::new("multiline", None, span),
1738            DocTag::new("maxWidth", Some("500".to_string()), span),
1739        ];
1740
1741        let meta = FieldMetadata::from_tags(&tags);
1742
1743        assert_eq!(meta.group, Some("Personal Info".to_string()));
1744        assert_eq!(meta.format, Some("date".to_string()));
1745        assert_eq!(meta.input_type, Some("textarea".to_string()));
1746        assert!(meta.multiline);
1747        assert_eq!(meta.max_width, Some(500));
1748    }
1749
1750    #[test]
1751    fn test_deprecation_info() {
1752        let info = DeprecationInfo::new("Field is deprecated")
1753            .since("2.0.0")
1754            .replacement("newField");
1755
1756        assert_eq!(info.message, "Field is deprecated");
1757        assert_eq!(info.since, Some("2.0.0".to_string()));
1758        assert_eq!(info.replacement, Some("newField".to_string()));
1759
1760        let formatted = info.format_message();
1761        assert!(formatted.contains("Field is deprecated"));
1762        assert!(formatted.contains("since 2.0.0"));
1763        assert!(formatted.contains("Use newField instead"));
1764    }
1765
1766    #[test]
1767    fn test_visibility_levels() {
1768        assert!(Visibility::Public.is_public());
1769        assert!(Visibility::Public.is_admin_visible());
1770
1771        assert!(!Visibility::Internal.is_public());
1772        assert!(Visibility::Internal.is_admin_visible());
1773
1774        assert!(!Visibility::Hidden.is_public());
1775        assert!(!Visibility::Hidden.is_admin_visible());
1776
1777        assert!(!Visibility::Private.is_public());
1778        assert!(!Visibility::Private.is_admin_visible());
1779    }
1780
1781    #[test]
1782    fn test_visibility_from_str() {
1783        assert_eq!(Visibility::parse("public"), Some(Visibility::Public));
1784        assert_eq!(Visibility::parse("INTERNAL"), Some(Visibility::Internal));
1785        assert_eq!(Visibility::parse("Hidden"), Some(Visibility::Hidden));
1786        assert_eq!(Visibility::parse("private"), Some(Visibility::Private));
1787        assert_eq!(Visibility::parse("unknown"), None);
1788    }
1789
1790    #[test]
1791    fn test_field_permissions_all() {
1792        let perms = FieldPermissions::all();
1793        assert!(perms.read);
1794        assert!(perms.create);
1795        assert!(perms.update);
1796        assert!(perms.filter);
1797        assert!(perms.sort);
1798    }
1799
1800    #[test]
1801    fn test_field_permissions_readonly() {
1802        let perms = FieldPermissions::readonly();
1803        assert!(perms.read);
1804        assert!(!perms.create);
1805        assert!(!perms.update);
1806        assert!(perms.filter);
1807        assert!(perms.sort);
1808    }
1809
1810    #[test]
1811    fn test_field_permissions_writeonly() {
1812        let perms = FieldPermissions::writeonly();
1813        assert!(!perms.read);
1814        assert!(perms.create);
1815        assert!(perms.update);
1816        assert!(!perms.filter);
1817        assert!(!perms.sort);
1818    }
1819
1820    #[test]
1821    fn test_field_permissions_from_metadata() {
1822        let mut meta = FieldMetadata::new();
1823        meta.readonly = true;
1824
1825        let perms = FieldPermissions::from_metadata(&meta);
1826        assert!(perms.read);
1827        assert!(!perms.create);
1828        assert!(!perms.update);
1829
1830        let mut sensitive_meta = FieldMetadata::new();
1831        sensitive_meta.sensitive = true;
1832
1833        let sensitive_perms = FieldPermissions::from_metadata(&sensitive_meta);
1834        assert!(sensitive_perms.read);
1835        assert!(!sensitive_perms.filter);
1836        assert!(!sensitive_perms.sort);
1837    }
1838
1839    #[test]
1840    fn test_enhanced_documentation_metadata_extraction() {
1841        let raw = r#"User's password hash
1842@hidden
1843@sensitive
1844@writeonly
1845@label Password
1846@since 1.0.0"#;
1847
1848        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1849
1850        assert!(doc.is_hidden());
1851        assert!(doc.is_sensitive());
1852        assert!(doc.is_writeonly());
1853        assert_eq!(doc.label(), Some("Password"));
1854        assert_eq!(doc.since(), Some("1.0.0"));
1855
1856        let meta = doc.extract_metadata();
1857        assert!(meta.hidden);
1858        assert!(meta.sensitive);
1859        assert!(meta.writeonly);
1860    }
1861
1862    #[test]
1863    fn test_enhanced_documentation_examples() {
1864        let raw = r#"Email address
1865@example user@example.com
1866@example admin@company.org
1867@placeholder Enter your email"#;
1868
1869        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1870
1871        let examples = doc.examples();
1872        assert_eq!(examples.len(), 2);
1873        assert_eq!(examples[0], "user@example.com");
1874        assert_eq!(examples[1], "admin@company.org");
1875        assert_eq!(doc.placeholder(), Some("Enter your email"));
1876    }
1877
1878    #[test]
1879    fn test_enhanced_documentation_deprecation() {
1880        let raw = r#"Old email field
1881@deprecated Use newEmail instead
1882@since 1.0.0"#;
1883
1884        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1885
1886        assert!(doc.is_deprecated());
1887        let info = doc.deprecation_info().unwrap();
1888        assert_eq!(info.message, "Use newEmail instead");
1889        assert_eq!(info.since, Some("1.0.0".to_string()));
1890    }
1891
1892    #[test]
1893    fn test_enhanced_documentation_group() {
1894        let raw = r#"User's display name
1895@group Personal Information
1896@format text"#;
1897
1898        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1899
1900        assert_eq!(doc.group(), Some("Personal Information"));
1901        let meta = doc.extract_metadata();
1902        assert_eq!(meta.format, Some("text".to_string()));
1903    }
1904
1905    // ==================== Additional Validation Type Tests ====================
1906
1907    #[test]
1908    fn test_validation_rule_error_message_custom() {
1909        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1910            .with_message("Please provide a valid email");
1911        assert_eq!(rule.error_message("email"), "Please provide a valid email");
1912    }
1913
1914    #[test]
1915    fn test_validation_rule_error_message_default() {
1916        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1917        let msg = rule.error_message("email");
1918        assert!(msg.contains("email"));
1919    }
1920
1921    #[test]
1922    fn test_validation_rule_type_checks() {
1923        let email_rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1924        assert!(email_rule.is_string_rule());
1925        assert!(!email_rule.is_numeric_rule());
1926        assert!(!email_rule.is_array_rule());
1927        assert!(!email_rule.is_date_rule());
1928
1929        let min_rule = ValidationRule::new(ValidationType::Min(0.0), Span::new(0, 0));
1930        assert!(!min_rule.is_string_rule());
1931        assert!(min_rule.is_numeric_rule());
1932        assert!(!min_rule.is_array_rule());
1933
1934        let items_rule = ValidationRule::new(ValidationType::MinItems(1), Span::new(0, 0));
1935        assert!(!items_rule.is_string_rule());
1936        assert!(!items_rule.is_numeric_rule());
1937        assert!(items_rule.is_array_rule());
1938
1939        let past_rule = ValidationRule::new(ValidationType::Past, Span::new(0, 0));
1940        assert!(!past_rule.is_string_rule());
1941        assert!(!past_rule.is_numeric_rule());
1942        assert!(!past_rule.is_array_rule());
1943        assert!(past_rule.is_date_rule());
1944    }
1945
1946    #[test]
1947    fn test_validation_type_is_id_format_rule() {
1948        assert!(ValidationType::Uuid.is_id_format_rule());
1949        assert!(ValidationType::Cuid.is_id_format_rule());
1950        assert!(ValidationType::Cuid2.is_id_format_rule());
1951        assert!(ValidationType::NanoId.is_id_format_rule());
1952        assert!(ValidationType::Ulid.is_id_format_rule());
1953        assert!(!ValidationType::Email.is_id_format_rule());
1954    }
1955
1956    #[test]
1957    fn test_validation_type_default_messages_comprehensive() {
1958        // String validators
1959        assert!(
1960            ValidationType::Url
1961                .default_message("website")
1962                .contains("URL")
1963        );
1964        assert!(ValidationType::Cuid.default_message("id").contains("CUID"));
1965        assert!(
1966            ValidationType::Cuid2
1967                .default_message("id")
1968                .contains("CUID2")
1969        );
1970        assert!(
1971            ValidationType::NanoId
1972                .default_message("id")
1973                .contains("NanoId")
1974        );
1975        assert!(ValidationType::Ulid.default_message("id").contains("ULID"));
1976        assert!(
1977            ValidationType::Alpha
1978                .default_message("name")
1979                .contains("letters")
1980        );
1981        assert!(
1982            ValidationType::Alphanumeric
1983                .default_message("code")
1984                .contains("letters and numbers")
1985        );
1986        assert!(
1987            ValidationType::Lowercase
1988                .default_message("slug")
1989                .contains("lowercase")
1990        );
1991        assert!(
1992            ValidationType::Uppercase
1993                .default_message("code")
1994                .contains("uppercase")
1995        );
1996        assert!(
1997            ValidationType::Trim
1998                .default_message("text")
1999                .contains("whitespace")
2000        );
2001        assert!(
2002            ValidationType::NoWhitespace
2003                .default_message("username")
2004                .contains("whitespace")
2005        );
2006        assert!(ValidationType::Ip.default_message("address").contains("IP"));
2007        assert!(
2008            ValidationType::Ipv4
2009                .default_message("address")
2010                .contains("IPv4")
2011        );
2012        assert!(
2013            ValidationType::Ipv6
2014                .default_message("address")
2015                .contains("IPv6")
2016        );
2017        assert!(
2018            ValidationType::CreditCard
2019                .default_message("card")
2020                .contains("credit card")
2021        );
2022        assert!(
2023            ValidationType::Phone
2024                .default_message("phone")
2025                .contains("phone")
2026        );
2027        assert!(ValidationType::Slug.default_message("url").contains("slug"));
2028        assert!(
2029            ValidationType::Hex
2030                .default_message("color")
2031                .contains("hexadecimal")
2032        );
2033        assert!(
2034            ValidationType::Base64
2035                .default_message("data")
2036                .contains("base64")
2037        );
2038        assert!(
2039            ValidationType::Json
2040                .default_message("config")
2041                .contains("JSON")
2042        );
2043        assert!(
2044            ValidationType::StartsWith("test".to_string())
2045                .default_message("field")
2046                .contains("start with")
2047        );
2048        assert!(
2049            ValidationType::EndsWith(".json".to_string())
2050                .default_message("file")
2051                .contains("end with")
2052        );
2053        assert!(
2054            ValidationType::Contains("keyword".to_string())
2055                .default_message("text")
2056                .contains("contain")
2057        );
2058        assert!(
2059            ValidationType::Length { min: 5, max: 10 }
2060                .default_message("text")
2061                .contains("between")
2062        );
2063
2064        // Numeric validators
2065        assert!(
2066            ValidationType::Negative
2067                .default_message("balance")
2068                .contains("negative")
2069        );
2070        assert!(
2071            ValidationType::NonNegative
2072                .default_message("count")
2073                .contains("not be negative")
2074        );
2075        assert!(
2076            ValidationType::NonPositive
2077                .default_message("debt")
2078                .contains("not be positive")
2079        );
2080        assert!(
2081            ValidationType::Integer
2082                .default_message("count")
2083                .contains("integer")
2084        );
2085        assert!(
2086            ValidationType::MultipleOf(5.0)
2087                .default_message("value")
2088                .contains("multiple")
2089        );
2090        assert!(
2091            ValidationType::Finite
2092                .default_message("value")
2093                .contains("finite")
2094        );
2095
2096        // Array validators
2097        assert!(
2098            ValidationType::MaxItems(10)
2099                .default_message("items")
2100                .contains("at most")
2101        );
2102        assert!(
2103            ValidationType::Items { min: 1, max: 5 }
2104                .default_message("tags")
2105                .contains("between")
2106        );
2107        assert!(
2108            ValidationType::Unique
2109                .default_message("items")
2110                .contains("unique")
2111        );
2112
2113        // Date validators
2114        assert!(
2115            ValidationType::Future
2116                .default_message("expiry")
2117                .contains("future")
2118        );
2119        assert!(
2120            ValidationType::PastOrPresent
2121                .default_message("login")
2122                .contains("not be in the future")
2123        );
2124        assert!(
2125            ValidationType::FutureOrPresent
2126                .default_message("deadline")
2127                .contains("not be in the past")
2128        );
2129        assert!(
2130            ValidationType::Before("2025-01-01".to_string())
2131                .default_message("date")
2132                .contains("before")
2133        );
2134
2135        // General validators
2136        assert!(
2137            ValidationType::Required
2138                .default_message("field")
2139                .contains("required")
2140        );
2141        assert!(
2142            ValidationType::NotEmpty
2143                .default_message("list")
2144                .contains("not be empty")
2145        );
2146        assert!(
2147            ValidationType::Custom("strongPassword".to_string())
2148                .default_message("password")
2149                .contains("custom")
2150        );
2151    }
2152
2153    #[test]
2154    fn test_validation_type_validator_names() {
2155        assert_eq!(ValidationType::Url.validator_name(), "url");
2156        assert_eq!(ValidationType::Cuid.validator_name(), "cuid");
2157        assert_eq!(ValidationType::Cuid2.validator_name(), "cuid2");
2158        assert_eq!(ValidationType::NanoId.validator_name(), "nanoid");
2159        assert_eq!(ValidationType::Ulid.validator_name(), "ulid");
2160        assert_eq!(ValidationType::Alpha.validator_name(), "alpha");
2161        assert_eq!(
2162            ValidationType::Alphanumeric.validator_name(),
2163            "alphanumeric"
2164        );
2165        assert_eq!(ValidationType::Lowercase.validator_name(), "lowercase");
2166        assert_eq!(ValidationType::Uppercase.validator_name(), "uppercase");
2167        assert_eq!(ValidationType::Trim.validator_name(), "trim");
2168        assert_eq!(
2169            ValidationType::NoWhitespace.validator_name(),
2170            "no_whitespace"
2171        );
2172        assert_eq!(ValidationType::Ip.validator_name(), "ip");
2173        assert_eq!(ValidationType::Ipv4.validator_name(), "ipv4");
2174        assert_eq!(ValidationType::Ipv6.validator_name(), "ipv6");
2175        assert_eq!(ValidationType::CreditCard.validator_name(), "credit_card");
2176        assert_eq!(ValidationType::Phone.validator_name(), "phone");
2177        assert_eq!(ValidationType::Slug.validator_name(), "slug");
2178        assert_eq!(ValidationType::Hex.validator_name(), "hex");
2179        assert_eq!(ValidationType::Base64.validator_name(), "base64");
2180        assert_eq!(ValidationType::Json.validator_name(), "json");
2181        assert_eq!(
2182            ValidationType::StartsWith("".to_string()).validator_name(),
2183            "starts_with"
2184        );
2185        assert_eq!(
2186            ValidationType::EndsWith("".to_string()).validator_name(),
2187            "ends_with"
2188        );
2189        assert_eq!(
2190            ValidationType::Contains("".to_string()).validator_name(),
2191            "contains"
2192        );
2193        assert_eq!(
2194            ValidationType::Length { min: 0, max: 0 }.validator_name(),
2195            "length"
2196        );
2197        assert_eq!(ValidationType::Max(0.0).validator_name(), "max");
2198        assert_eq!(ValidationType::Negative.validator_name(), "negative");
2199        assert_eq!(ValidationType::NonNegative.validator_name(), "non_negative");
2200        assert_eq!(ValidationType::NonPositive.validator_name(), "non_positive");
2201        assert_eq!(ValidationType::Integer.validator_name(), "integer");
2202        assert_eq!(
2203            ValidationType::MultipleOf(0.0).validator_name(),
2204            "multiple_of"
2205        );
2206        assert_eq!(ValidationType::Finite.validator_name(), "finite");
2207        assert_eq!(ValidationType::MaxItems(0).validator_name(), "max_items");
2208        assert_eq!(
2209            ValidationType::Items { min: 0, max: 0 }.validator_name(),
2210            "items"
2211        );
2212        assert_eq!(ValidationType::Unique.validator_name(), "unique");
2213        assert_eq!(ValidationType::NonEmpty.validator_name(), "non_empty");
2214        assert_eq!(ValidationType::Future.validator_name(), "future");
2215        assert_eq!(
2216            ValidationType::PastOrPresent.validator_name(),
2217            "past_or_present"
2218        );
2219        assert_eq!(
2220            ValidationType::FutureOrPresent.validator_name(),
2221            "future_or_present"
2222        );
2223        assert_eq!(
2224            ValidationType::After("".to_string()).validator_name(),
2225            "after"
2226        );
2227        assert_eq!(
2228            ValidationType::Before("".to_string()).validator_name(),
2229            "before"
2230        );
2231        assert_eq!(ValidationType::Required.validator_name(), "required");
2232        assert_eq!(ValidationType::NotEmpty.validator_name(), "not_empty");
2233        assert_eq!(ValidationType::OneOf(vec![]).validator_name(), "one_of");
2234        assert_eq!(
2235            ValidationType::Custom("".to_string()).validator_name(),
2236            "custom"
2237        );
2238    }
2239
2240    #[test]
2241    fn test_field_validation_has_rules() {
2242        let mut validation = FieldValidation::new();
2243        assert!(!validation.has_numeric_rules());
2244        assert!(!validation.has_array_rules());
2245
2246        validation.add_rule(ValidationRule::new(
2247            ValidationType::Min(0.0),
2248            Span::new(0, 0),
2249        ));
2250        assert!(validation.has_numeric_rules());
2251
2252        let mut arr_validation = FieldValidation::new();
2253        arr_validation.add_rule(ValidationRule::new(
2254            ValidationType::MinItems(1),
2255            Span::new(0, 0),
2256        ));
2257        assert!(arr_validation.has_array_rules());
2258
2259        // Date rules are checked via rule type
2260        let mut date_validation = FieldValidation::new();
2261        date_validation.add_rule(ValidationRule::new(ValidationType::Past, Span::new(0, 0)));
2262        assert!(date_validation.rules.iter().any(|r| r.is_date_rule()));
2263    }
2264
2265    #[test]
2266    fn test_parse_validation_rule_more_validators() {
2267        let span = Span::new(0, 0);
2268
2269        // String validators
2270        let url = parse_validation_rule("url", span).unwrap();
2271        assert!(matches!(url.rule_type, ValidationType::Url));
2272
2273        let cuid = parse_validation_rule("cuid", span).unwrap();
2274        assert!(matches!(cuid.rule_type, ValidationType::Cuid));
2275
2276        let cuid2 = parse_validation_rule("cuid2", span).unwrap();
2277        assert!(matches!(cuid2.rule_type, ValidationType::Cuid2));
2278
2279        let nanoid = parse_validation_rule("nanoid", span).unwrap();
2280        assert!(matches!(nanoid.rule_type, ValidationType::NanoId));
2281
2282        let ulid = parse_validation_rule("ulid", span).unwrap();
2283        assert!(matches!(ulid.rule_type, ValidationType::Ulid));
2284
2285        let alpha = parse_validation_rule("alpha", span).unwrap();
2286        assert!(matches!(alpha.rule_type, ValidationType::Alpha));
2287
2288        let alphanumeric = parse_validation_rule("alphanumeric", span).unwrap();
2289        assert!(matches!(
2290            alphanumeric.rule_type,
2291            ValidationType::Alphanumeric
2292        ));
2293
2294        let lowercase = parse_validation_rule("lowercase", span).unwrap();
2295        assert!(matches!(lowercase.rule_type, ValidationType::Lowercase));
2296
2297        let uppercase = parse_validation_rule("uppercase", span).unwrap();
2298        assert!(matches!(uppercase.rule_type, ValidationType::Uppercase));
2299
2300        let trim = parse_validation_rule("trim", span).unwrap();
2301        assert!(matches!(trim.rule_type, ValidationType::Trim));
2302
2303        let no_whitespace = parse_validation_rule("noWhitespace", span).unwrap();
2304        assert!(matches!(
2305            no_whitespace.rule_type,
2306            ValidationType::NoWhitespace
2307        ));
2308
2309        let ip = parse_validation_rule("ip", span).unwrap();
2310        assert!(matches!(ip.rule_type, ValidationType::Ip));
2311
2312        let ipv4 = parse_validation_rule("ipv4", span).unwrap();
2313        assert!(matches!(ipv4.rule_type, ValidationType::Ipv4));
2314
2315        let ipv6 = parse_validation_rule("ipv6", span).unwrap();
2316        assert!(matches!(ipv6.rule_type, ValidationType::Ipv6));
2317
2318        let credit_card = parse_validation_rule("creditCard", span).unwrap();
2319        assert!(matches!(credit_card.rule_type, ValidationType::CreditCard));
2320
2321        let phone = parse_validation_rule("phone", span).unwrap();
2322        assert!(matches!(phone.rule_type, ValidationType::Phone));
2323
2324        let slug = parse_validation_rule("slug", span).unwrap();
2325        assert!(matches!(slug.rule_type, ValidationType::Slug));
2326
2327        let hex = parse_validation_rule("hex", span).unwrap();
2328        assert!(matches!(hex.rule_type, ValidationType::Hex));
2329
2330        let base64 = parse_validation_rule("base64", span).unwrap();
2331        assert!(matches!(base64.rule_type, ValidationType::Base64));
2332
2333        let json = parse_validation_rule("json", span).unwrap();
2334        assert!(matches!(json.rule_type, ValidationType::Json));
2335
2336        // Numeric validators
2337        let negative = parse_validation_rule("negative", span).unwrap();
2338        assert!(matches!(negative.rule_type, ValidationType::Negative));
2339
2340        let non_negative = parse_validation_rule("nonNegative", span).unwrap();
2341        assert!(matches!(
2342            non_negative.rule_type,
2343            ValidationType::NonNegative
2344        ));
2345
2346        let non_positive = parse_validation_rule("nonPositive", span).unwrap();
2347        assert!(matches!(
2348            non_positive.rule_type,
2349            ValidationType::NonPositive
2350        ));
2351
2352        let integer = parse_validation_rule("integer", span).unwrap();
2353        assert!(matches!(integer.rule_type, ValidationType::Integer));
2354
2355        let finite = parse_validation_rule("finite", span).unwrap();
2356        assert!(matches!(finite.rule_type, ValidationType::Finite));
2357
2358        // Array validators
2359        let unique = parse_validation_rule("unique", span).unwrap();
2360        assert!(matches!(unique.rule_type, ValidationType::Unique));
2361
2362        let non_empty = parse_validation_rule("nonEmpty", span).unwrap();
2363        assert!(matches!(non_empty.rule_type, ValidationType::NonEmpty));
2364
2365        // Date validators
2366        let past = parse_validation_rule("past", span).unwrap();
2367        assert!(matches!(past.rule_type, ValidationType::Past));
2368
2369        let future = parse_validation_rule("future", span).unwrap();
2370        assert!(matches!(future.rule_type, ValidationType::Future));
2371
2372        let past_or_present = parse_validation_rule("pastOrPresent", span).unwrap();
2373        assert!(matches!(
2374            past_or_present.rule_type,
2375            ValidationType::PastOrPresent
2376        ));
2377
2378        let future_or_present = parse_validation_rule("futureOrPresent", span).unwrap();
2379        assert!(matches!(
2380            future_or_present.rule_type,
2381            ValidationType::FutureOrPresent
2382        ));
2383
2384        // General validators
2385        let required = parse_validation_rule("required", span).unwrap();
2386        assert!(matches!(required.rule_type, ValidationType::Required));
2387
2388        let not_empty = parse_validation_rule("notEmpty", span).unwrap();
2389        assert!(matches!(not_empty.rule_type, ValidationType::NotEmpty));
2390    }
2391
2392    #[test]
2393    fn test_parse_validation_rule_with_string_args() {
2394        let span = Span::new(0, 0);
2395
2396        let starts_with = parse_validation_rule(r#"startsWith("PREFIX_")"#, span).unwrap();
2397        if let ValidationType::StartsWith(prefix) = starts_with.rule_type {
2398            assert_eq!(prefix, "PREFIX_");
2399        } else {
2400            panic!("Expected StartsWith");
2401        }
2402
2403        let ends_with = parse_validation_rule(r#"endsWith(".json")"#, span).unwrap();
2404        if let ValidationType::EndsWith(suffix) = ends_with.rule_type {
2405            assert_eq!(suffix, ".json");
2406        } else {
2407            panic!("Expected EndsWith");
2408        }
2409
2410        let contains = parse_validation_rule(r#"contains("keyword")"#, span).unwrap();
2411        if let ValidationType::Contains(substring) = contains.rule_type {
2412            assert_eq!(substring, "keyword");
2413        } else {
2414            panic!("Expected Contains");
2415        }
2416
2417        let custom = parse_validation_rule(r#"custom("myValidator")"#, span).unwrap();
2418        if let ValidationType::Custom(name) = custom.rule_type {
2419            assert_eq!(name, "myValidator");
2420        } else {
2421            panic!("Expected Custom");
2422        }
2423
2424        let after = parse_validation_rule(r#"after("2024-01-01")"#, span).unwrap();
2425        if let ValidationType::After(date) = after.rule_type {
2426            assert_eq!(date, "2024-01-01");
2427        } else {
2428            panic!("Expected After");
2429        }
2430
2431        let before = parse_validation_rule(r#"before("2025-12-31")"#, span).unwrap();
2432        if let ValidationType::Before(date) = before.rule_type {
2433            assert_eq!(date, "2025-12-31");
2434        } else {
2435            panic!("Expected Before");
2436        }
2437    }
2438
2439    #[test]
2440    fn test_parse_validation_rule_numeric_args() {
2441        let span = Span::new(0, 0);
2442
2443        let min = parse_validation_rule("min(10)", span).unwrap();
2444        if let ValidationType::Min(n) = min.rule_type {
2445            assert!((n - 10.0).abs() < f64::EPSILON);
2446        } else {
2447            panic!("Expected Min");
2448        }
2449
2450        let max = parse_validation_rule("max(100)", span).unwrap();
2451        if let ValidationType::Max(n) = max.rule_type {
2452            assert!((n - 100.0).abs() < f64::EPSILON);
2453        } else {
2454            panic!("Expected Max");
2455        }
2456
2457        let multiple_of = parse_validation_rule("multipleOf(5)", span).unwrap();
2458        if let ValidationType::MultipleOf(n) = multiple_of.rule_type {
2459            assert!((n - 5.0).abs() < f64::EPSILON);
2460        } else {
2461            panic!("Expected MultipleOf");
2462        }
2463
2464        let min_items = parse_validation_rule("minItems(1)", span).unwrap();
2465        assert!(matches!(min_items.rule_type, ValidationType::MinItems(1)));
2466
2467        let max_items = parse_validation_rule("maxItems(10)", span).unwrap();
2468        assert!(matches!(max_items.rule_type, ValidationType::MaxItems(10)));
2469
2470        let length = parse_validation_rule("length(5, 100)", span).unwrap();
2471        if let ValidationType::Length { min, max } = length.rule_type {
2472            assert_eq!(min, 5);
2473            assert_eq!(max, 100);
2474        } else {
2475            panic!("Expected Length");
2476        }
2477
2478        let items = parse_validation_rule("items(1, 10)", span).unwrap();
2479        if let ValidationType::Items { min, max } = items.rule_type {
2480            assert_eq!(min, 1);
2481            assert_eq!(max, 10);
2482        } else {
2483            panic!("Expected Items");
2484        }
2485    }
2486
2487    #[test]
2488    fn test_parse_validation_rule_unknown() {
2489        let span = Span::new(0, 0);
2490        assert!(parse_validation_rule("unknownValidator", span).is_none());
2491    }
2492
2493    #[test]
2494    fn test_field_metadata_more_tags() {
2495        let span = Span::new(0, 0);
2496        let tags = vec![
2497            DocTag::new("internal", None, span),
2498            DocTag::new(
2499                "description",
2500                Some("A detailed description".to_string()),
2501                span,
2502            ),
2503            DocTag::new("seeAlso", Some("otherField".to_string()), span),
2504            DocTag::new("omitFromInput", None, span),
2505            DocTag::new("omitFromOutput", None, span),
2506        ];
2507
2508        let meta = FieldMetadata::from_tags(&tags);
2509
2510        assert!(meta.internal);
2511        assert_eq!(meta.description, Some("A detailed description".to_string()));
2512        assert_eq!(meta.see_also, vec!["otherField".to_string()]);
2513        assert!(meta.omit_from_input);
2514        assert!(meta.omit_from_output);
2515    }
2516
2517    #[test]
2518    fn test_field_permissions_none() {
2519        let perms = FieldPermissions::none();
2520        assert!(!perms.read);
2521        assert!(!perms.create);
2522        assert!(!perms.update);
2523        assert!(!perms.filter);
2524        assert!(!perms.sort);
2525    }
2526
2527    #[test]
2528    fn test_enhanced_documentation_no_validation() {
2529        let raw = "Just a simple description";
2530        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2531
2532        assert_eq!(doc.text, "Just a simple description");
2533        assert!(!doc.has_validation());
2534        assert_eq!(doc.validation.len(), 0);
2535        assert!(doc.tags.is_empty());
2536    }
2537
2538    #[test]
2539    fn test_enhanced_documentation_readonly() {
2540        let raw = r#"ID field
2541@readonly"#;
2542
2543        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2544
2545        assert!(doc.is_readonly());
2546        assert!(!doc.is_hidden());
2547        assert!(!doc.is_sensitive());
2548    }
2549}