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