Skip to main content

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//! ```text
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)]
1420// Float literals like `3.14` are sample validation values, not math constants.
1421#[allow(clippy::approx_constant)]
1422mod tests {
1423    use super::*;
1424
1425    #[test]
1426    fn test_validation_rule_new() {
1427        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1428        assert!(matches!(rule.rule_type, ValidationType::Email));
1429        assert!(rule.message.is_none());
1430    }
1431
1432    #[test]
1433    fn test_validation_rule_with_message() {
1434        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1435            .with_message("Please enter a valid email");
1436        assert_eq!(rule.message, Some("Please enter a valid email".to_string()));
1437    }
1438
1439    #[test]
1440    fn test_validation_type_default_messages() {
1441        let email_msg = ValidationType::Email.default_message("email");
1442        assert!(email_msg.contains("email"));
1443        assert!(email_msg.contains("valid"));
1444
1445        let min_msg = ValidationType::Min(10.0).default_message("age");
1446        assert!(min_msg.contains("age"));
1447        assert!(min_msg.contains("10"));
1448    }
1449
1450    #[test]
1451    fn test_validation_type_is_string_rule() {
1452        assert!(ValidationType::Email.is_string_rule());
1453        assert!(ValidationType::Regex(".*".to_string()).is_string_rule());
1454        assert!(!ValidationType::Min(0.0).is_string_rule());
1455    }
1456
1457    #[test]
1458    fn test_validation_type_is_numeric_rule() {
1459        assert!(ValidationType::Min(0.0).is_numeric_rule());
1460        assert!(ValidationType::Positive.is_numeric_rule());
1461        assert!(!ValidationType::Email.is_numeric_rule());
1462    }
1463
1464    #[test]
1465    fn test_validation_type_is_array_rule() {
1466        assert!(ValidationType::MinItems(1).is_array_rule());
1467        assert!(ValidationType::Unique.is_array_rule());
1468        assert!(!ValidationType::Email.is_array_rule());
1469    }
1470
1471    #[test]
1472    fn test_validation_type_is_date_rule() {
1473        assert!(ValidationType::Past.is_date_rule());
1474        assert!(ValidationType::After("2024-01-01".to_string()).is_date_rule());
1475        assert!(!ValidationType::Email.is_date_rule());
1476    }
1477
1478    #[test]
1479    fn test_field_validation() {
1480        let mut validation = FieldValidation::new();
1481        assert!(validation.is_empty());
1482
1483        validation.add_rule(ValidationRule::new(ValidationType::Email, Span::new(0, 0)));
1484        validation.add_rule(ValidationRule::new(
1485            ValidationType::MaxLength(255),
1486            Span::new(0, 0),
1487        ));
1488
1489        assert_eq!(validation.len(), 2);
1490        assert!(!validation.is_empty());
1491        assert!(validation.has_string_rules());
1492    }
1493
1494    #[test]
1495    fn test_field_validation_is_required() {
1496        let mut validation = FieldValidation::new();
1497        assert!(!validation.is_required());
1498
1499        validation.add_rule(ValidationRule::new(
1500            ValidationType::Required,
1501            Span::new(0, 0),
1502        ));
1503        assert!(validation.is_required());
1504    }
1505
1506    #[test]
1507    fn test_parse_validation_rule_simple() {
1508        let span = Span::new(0, 0);
1509
1510        let email = parse_validation_rule("email", span).unwrap();
1511        assert!(matches!(email.rule_type, ValidationType::Email));
1512
1513        let uuid = parse_validation_rule("uuid", span).unwrap();
1514        assert!(matches!(uuid.rule_type, ValidationType::Uuid));
1515
1516        let positive = parse_validation_rule("positive", span).unwrap();
1517        assert!(matches!(positive.rule_type, ValidationType::Positive));
1518    }
1519
1520    #[test]
1521    fn test_parse_validation_rule_with_args() {
1522        let span = Span::new(0, 0);
1523
1524        let min_length = parse_validation_rule("minLength(5)", span).unwrap();
1525        assert!(matches!(min_length.rule_type, ValidationType::MinLength(5)));
1526
1527        let max = parse_validation_rule("max(100)", span).unwrap();
1528        assert!(
1529            matches!(max.rule_type, ValidationType::Max(n) if (n - 100.0).abs() < f64::EPSILON)
1530        );
1531
1532        let range = parse_validation_rule("range(0, 100)", span).unwrap();
1533        if let ValidationType::Range { min, max } = range.rule_type {
1534            assert!((min - 0.0).abs() < f64::EPSILON);
1535            assert!((max - 100.0).abs() < f64::EPSILON);
1536        } else {
1537            panic!("Expected Range");
1538        }
1539    }
1540
1541    #[test]
1542    fn test_parse_validation_rule_regex() {
1543        let span = Span::new(0, 0);
1544
1545        let regex = parse_validation_rule(r#"regex("^[a-z]+$")"#, span).unwrap();
1546        if let ValidationType::Regex(pattern) = regex.rule_type {
1547            assert_eq!(pattern, "^[a-z]+$");
1548        } else {
1549            panic!("Expected Regex");
1550        }
1551    }
1552
1553    #[test]
1554    fn test_parse_validation_rule_one_of() {
1555        let span = Span::new(0, 0);
1556
1557        let one_of = parse_validation_rule(r#"oneOf("a", "b", "c")"#, span).unwrap();
1558        if let ValidationType::OneOf(values) = one_of.rule_type {
1559            assert_eq!(values.len(), 3);
1560            assert_eq!(values[0], ValidationValue::String("a".to_string()));
1561        } else {
1562            panic!("Expected OneOf");
1563        }
1564    }
1565
1566    #[test]
1567    fn test_parse_validation_value() {
1568        assert_eq!(
1569            parse_validation_value("\"hello\""),
1570            Some(ValidationValue::String("hello".to_string()))
1571        );
1572        assert_eq!(parse_validation_value("42"), Some(ValidationValue::Int(42)));
1573        assert_eq!(
1574            parse_validation_value("3.14"),
1575            Some(ValidationValue::Float(3.14))
1576        );
1577        assert_eq!(
1578            parse_validation_value("true"),
1579            Some(ValidationValue::Bool(true))
1580        );
1581    }
1582
1583    #[test]
1584    fn test_enhanced_documentation_parse() {
1585        let raw = r#"The user's email address
1586@validate: email, maxLength(255)
1587@deprecated Use newEmail instead"#;
1588
1589        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1590
1591        assert_eq!(doc.text, "The user's email address");
1592        assert!(doc.has_validation());
1593        assert_eq!(doc.validation.len(), 2);
1594        assert_eq!(doc.tags.len(), 1);
1595        assert_eq!(doc.tags[0].name.as_str(), "deprecated");
1596    }
1597
1598    #[test]
1599    fn test_enhanced_documentation_multiple_validate_lines() {
1600        let raw = r#"Username must be valid
1601@validate: minLength(3), maxLength(30)
1602@validate: regex("^[a-z0-9_]+$")"#;
1603
1604        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1605
1606        assert_eq!(doc.text, "Username must be valid");
1607        assert_eq!(doc.validation.len(), 3);
1608    }
1609
1610    #[test]
1611    fn test_doc_tag_parsing() {
1612        let span = Span::new(0, 0);
1613
1614        let tag = parse_doc_tag("@deprecated Use newField instead", span).unwrap();
1615        assert_eq!(tag.name.as_str(), "deprecated");
1616        assert_eq!(tag.value, Some("Use newField instead".to_string()));
1617
1618        let tag_no_value = parse_doc_tag("@internal", span).unwrap();
1619        assert_eq!(tag_no_value.name.as_str(), "internal");
1620        assert!(tag_no_value.value.is_none());
1621    }
1622
1623    #[test]
1624    fn test_validation_value_display() {
1625        assert_eq!(
1626            format!("{}", ValidationValue::String("test".to_string())),
1627            "\"test\""
1628        );
1629        assert_eq!(format!("{}", ValidationValue::Int(42)), "42");
1630        assert_eq!(format!("{}", ValidationValue::Float(3.14)), "3.14");
1631        assert_eq!(format!("{}", ValidationValue::Bool(true)), "true");
1632    }
1633
1634    #[test]
1635    fn test_validator_name() {
1636        assert_eq!(ValidationType::Email.validator_name(), "email");
1637        assert_eq!(ValidationType::MinLength(5).validator_name(), "min_length");
1638        assert_eq!(
1639            ValidationType::Range {
1640                min: 0.0,
1641                max: 100.0
1642            }
1643            .validator_name(),
1644            "range"
1645        );
1646    }
1647
1648    // ==================== Field Metadata Tests ====================
1649
1650    #[test]
1651    fn test_field_metadata_default() {
1652        let meta = FieldMetadata::new();
1653        assert!(!meta.hidden);
1654        assert!(!meta.internal);
1655        assert!(!meta.sensitive);
1656        assert!(!meta.readonly);
1657        assert!(!meta.writeonly);
1658        assert!(meta.deprecated.is_none());
1659        assert!(meta.label.is_none());
1660        assert!(meta.examples.is_empty());
1661    }
1662
1663    #[test]
1664    fn test_field_metadata_from_tags() {
1665        let span = Span::new(0, 0);
1666        let tags = vec![
1667            DocTag::new("hidden", None, span),
1668            DocTag::new("sensitive", None, span),
1669            DocTag::new("label", Some("User ID".to_string()), span),
1670            DocTag::new("example", Some("12345".to_string()), span),
1671            DocTag::new("example", Some("67890".to_string()), span),
1672        ];
1673
1674        let meta = FieldMetadata::from_tags(&tags);
1675
1676        assert!(meta.hidden);
1677        assert!(meta.sensitive);
1678        assert_eq!(meta.label, Some("User ID".to_string()));
1679        assert_eq!(meta.examples.len(), 2);
1680        assert_eq!(meta.examples[0], "12345");
1681        assert_eq!(meta.examples[1], "67890");
1682    }
1683
1684    #[test]
1685    fn test_field_metadata_deprecated() {
1686        let span = Span::new(0, 0);
1687        let tags = vec![DocTag::new(
1688            "deprecated",
1689            Some("Use newField instead".to_string()),
1690            span,
1691        )];
1692
1693        let meta = FieldMetadata::from_tags(&tags);
1694
1695        assert!(meta.is_deprecated());
1696        assert_eq!(meta.deprecation_message(), Some("Use newField instead"));
1697    }
1698
1699    #[test]
1700    fn test_field_metadata_readonly_writeonly() {
1701        let span = Span::new(0, 0);
1702
1703        let readonly_tags = vec![DocTag::new("readonly", None, span)];
1704        let readonly_meta = FieldMetadata::from_tags(&readonly_tags);
1705        assert!(readonly_meta.readonly);
1706        assert!(readonly_meta.should_omit_from_input());
1707        assert!(!readonly_meta.should_omit_from_output());
1708
1709        let writeonly_tags = vec![DocTag::new("writeonly", None, span)];
1710        let writeonly_meta = FieldMetadata::from_tags(&writeonly_tags);
1711        assert!(writeonly_meta.writeonly);
1712        assert!(writeonly_meta.should_omit_from_output());
1713        assert!(!writeonly_meta.should_omit_from_input());
1714    }
1715
1716    #[test]
1717    fn test_field_metadata_serialization() {
1718        let span = Span::new(0, 0);
1719        let tags = vec![
1720            DocTag::new("alias", Some("userId".to_string()), span),
1721            DocTag::new("serializedName", Some("user_id".to_string()), span),
1722            DocTag::new("order", Some("1".to_string()), span),
1723        ];
1724
1725        let meta = FieldMetadata::from_tags(&tags);
1726
1727        assert_eq!(meta.alias, Some("userId".to_string()));
1728        assert_eq!(meta.serialized_name, Some("user_id".to_string()));
1729        assert_eq!(meta.order, Some(1));
1730    }
1731
1732    #[test]
1733    fn test_field_metadata_ui_hints() {
1734        let span = Span::new(0, 0);
1735        let tags = vec![
1736            DocTag::new("group", Some("Personal Info".to_string()), span),
1737            DocTag::new("format", Some("date".to_string()), span),
1738            DocTag::new("inputType", Some("textarea".to_string()), span),
1739            DocTag::new("multiline", None, span),
1740            DocTag::new("maxWidth", Some("500".to_string()), span),
1741        ];
1742
1743        let meta = FieldMetadata::from_tags(&tags);
1744
1745        assert_eq!(meta.group, Some("Personal Info".to_string()));
1746        assert_eq!(meta.format, Some("date".to_string()));
1747        assert_eq!(meta.input_type, Some("textarea".to_string()));
1748        assert!(meta.multiline);
1749        assert_eq!(meta.max_width, Some(500));
1750    }
1751
1752    #[test]
1753    fn test_deprecation_info() {
1754        let info = DeprecationInfo::new("Field is deprecated")
1755            .since("2.0.0")
1756            .replacement("newField");
1757
1758        assert_eq!(info.message, "Field is deprecated");
1759        assert_eq!(info.since, Some("2.0.0".to_string()));
1760        assert_eq!(info.replacement, Some("newField".to_string()));
1761
1762        let formatted = info.format_message();
1763        assert!(formatted.contains("Field is deprecated"));
1764        assert!(formatted.contains("since 2.0.0"));
1765        assert!(formatted.contains("Use newField instead"));
1766    }
1767
1768    #[test]
1769    fn test_visibility_levels() {
1770        assert!(Visibility::Public.is_public());
1771        assert!(Visibility::Public.is_admin_visible());
1772
1773        assert!(!Visibility::Internal.is_public());
1774        assert!(Visibility::Internal.is_admin_visible());
1775
1776        assert!(!Visibility::Hidden.is_public());
1777        assert!(!Visibility::Hidden.is_admin_visible());
1778
1779        assert!(!Visibility::Private.is_public());
1780        assert!(!Visibility::Private.is_admin_visible());
1781    }
1782
1783    #[test]
1784    fn test_visibility_from_str() {
1785        assert_eq!(Visibility::parse("public"), Some(Visibility::Public));
1786        assert_eq!(Visibility::parse("INTERNAL"), Some(Visibility::Internal));
1787        assert_eq!(Visibility::parse("Hidden"), Some(Visibility::Hidden));
1788        assert_eq!(Visibility::parse("private"), Some(Visibility::Private));
1789        assert_eq!(Visibility::parse("unknown"), None);
1790    }
1791
1792    #[test]
1793    fn test_field_permissions_all() {
1794        let perms = FieldPermissions::all();
1795        assert!(perms.read);
1796        assert!(perms.create);
1797        assert!(perms.update);
1798        assert!(perms.filter);
1799        assert!(perms.sort);
1800    }
1801
1802    #[test]
1803    fn test_field_permissions_readonly() {
1804        let perms = FieldPermissions::readonly();
1805        assert!(perms.read);
1806        assert!(!perms.create);
1807        assert!(!perms.update);
1808        assert!(perms.filter);
1809        assert!(perms.sort);
1810    }
1811
1812    #[test]
1813    fn test_field_permissions_writeonly() {
1814        let perms = FieldPermissions::writeonly();
1815        assert!(!perms.read);
1816        assert!(perms.create);
1817        assert!(perms.update);
1818        assert!(!perms.filter);
1819        assert!(!perms.sort);
1820    }
1821
1822    #[test]
1823    fn test_field_permissions_from_metadata() {
1824        let mut meta = FieldMetadata::new();
1825        meta.readonly = true;
1826
1827        let perms = FieldPermissions::from_metadata(&meta);
1828        assert!(perms.read);
1829        assert!(!perms.create);
1830        assert!(!perms.update);
1831
1832        let mut sensitive_meta = FieldMetadata::new();
1833        sensitive_meta.sensitive = true;
1834
1835        let sensitive_perms = FieldPermissions::from_metadata(&sensitive_meta);
1836        assert!(sensitive_perms.read);
1837        assert!(!sensitive_perms.filter);
1838        assert!(!sensitive_perms.sort);
1839    }
1840
1841    #[test]
1842    fn test_enhanced_documentation_metadata_extraction() {
1843        let raw = r#"User's password hash
1844@hidden
1845@sensitive
1846@writeonly
1847@label Password
1848@since 1.0.0"#;
1849
1850        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1851
1852        assert!(doc.is_hidden());
1853        assert!(doc.is_sensitive());
1854        assert!(doc.is_writeonly());
1855        assert_eq!(doc.label(), Some("Password"));
1856        assert_eq!(doc.since(), Some("1.0.0"));
1857
1858        let meta = doc.extract_metadata();
1859        assert!(meta.hidden);
1860        assert!(meta.sensitive);
1861        assert!(meta.writeonly);
1862    }
1863
1864    #[test]
1865    fn test_enhanced_documentation_examples() {
1866        let raw = r#"Email address
1867@example user@example.com
1868@example admin@company.org
1869@placeholder Enter your email"#;
1870
1871        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1872
1873        let examples = doc.examples();
1874        assert_eq!(examples.len(), 2);
1875        assert_eq!(examples[0], "user@example.com");
1876        assert_eq!(examples[1], "admin@company.org");
1877        assert_eq!(doc.placeholder(), Some("Enter your email"));
1878    }
1879
1880    #[test]
1881    fn test_enhanced_documentation_deprecation() {
1882        let raw = r#"Old email field
1883@deprecated Use newEmail instead
1884@since 1.0.0"#;
1885
1886        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1887
1888        assert!(doc.is_deprecated());
1889        let info = doc.deprecation_info().unwrap();
1890        assert_eq!(info.message, "Use newEmail instead");
1891        assert_eq!(info.since, Some("1.0.0".to_string()));
1892    }
1893
1894    #[test]
1895    fn test_enhanced_documentation_group() {
1896        let raw = r#"User's display name
1897@group Personal Information
1898@format text"#;
1899
1900        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1901
1902        assert_eq!(doc.group(), Some("Personal Information"));
1903        let meta = doc.extract_metadata();
1904        assert_eq!(meta.format, Some("text".to_string()));
1905    }
1906
1907    // ==================== Additional Validation Type Tests ====================
1908
1909    #[test]
1910    fn test_validation_rule_error_message_custom() {
1911        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1912            .with_message("Please provide a valid email");
1913        assert_eq!(rule.error_message("email"), "Please provide a valid email");
1914    }
1915
1916    #[test]
1917    fn test_validation_rule_error_message_default() {
1918        let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1919        let msg = rule.error_message("email");
1920        assert!(msg.contains("email"));
1921    }
1922
1923    #[test]
1924    fn test_validation_rule_type_checks() {
1925        let email_rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1926        assert!(email_rule.is_string_rule());
1927        assert!(!email_rule.is_numeric_rule());
1928        assert!(!email_rule.is_array_rule());
1929        assert!(!email_rule.is_date_rule());
1930
1931        let min_rule = ValidationRule::new(ValidationType::Min(0.0), Span::new(0, 0));
1932        assert!(!min_rule.is_string_rule());
1933        assert!(min_rule.is_numeric_rule());
1934        assert!(!min_rule.is_array_rule());
1935
1936        let items_rule = ValidationRule::new(ValidationType::MinItems(1), Span::new(0, 0));
1937        assert!(!items_rule.is_string_rule());
1938        assert!(!items_rule.is_numeric_rule());
1939        assert!(items_rule.is_array_rule());
1940
1941        let past_rule = ValidationRule::new(ValidationType::Past, Span::new(0, 0));
1942        assert!(!past_rule.is_string_rule());
1943        assert!(!past_rule.is_numeric_rule());
1944        assert!(!past_rule.is_array_rule());
1945        assert!(past_rule.is_date_rule());
1946    }
1947
1948    #[test]
1949    fn test_validation_type_is_id_format_rule() {
1950        assert!(ValidationType::Uuid.is_id_format_rule());
1951        assert!(ValidationType::Cuid.is_id_format_rule());
1952        assert!(ValidationType::Cuid2.is_id_format_rule());
1953        assert!(ValidationType::NanoId.is_id_format_rule());
1954        assert!(ValidationType::Ulid.is_id_format_rule());
1955        assert!(!ValidationType::Email.is_id_format_rule());
1956    }
1957
1958    #[test]
1959    fn test_validation_type_default_messages_comprehensive() {
1960        // String validators
1961        assert!(
1962            ValidationType::Url
1963                .default_message("website")
1964                .contains("URL")
1965        );
1966        assert!(ValidationType::Cuid.default_message("id").contains("CUID"));
1967        assert!(
1968            ValidationType::Cuid2
1969                .default_message("id")
1970                .contains("CUID2")
1971        );
1972        assert!(
1973            ValidationType::NanoId
1974                .default_message("id")
1975                .contains("NanoId")
1976        );
1977        assert!(ValidationType::Ulid.default_message("id").contains("ULID"));
1978        assert!(
1979            ValidationType::Alpha
1980                .default_message("name")
1981                .contains("letters")
1982        );
1983        assert!(
1984            ValidationType::Alphanumeric
1985                .default_message("code")
1986                .contains("letters and numbers")
1987        );
1988        assert!(
1989            ValidationType::Lowercase
1990                .default_message("slug")
1991                .contains("lowercase")
1992        );
1993        assert!(
1994            ValidationType::Uppercase
1995                .default_message("code")
1996                .contains("uppercase")
1997        );
1998        assert!(
1999            ValidationType::Trim
2000                .default_message("text")
2001                .contains("whitespace")
2002        );
2003        assert!(
2004            ValidationType::NoWhitespace
2005                .default_message("username")
2006                .contains("whitespace")
2007        );
2008        assert!(ValidationType::Ip.default_message("address").contains("IP"));
2009        assert!(
2010            ValidationType::Ipv4
2011                .default_message("address")
2012                .contains("IPv4")
2013        );
2014        assert!(
2015            ValidationType::Ipv6
2016                .default_message("address")
2017                .contains("IPv6")
2018        );
2019        assert!(
2020            ValidationType::CreditCard
2021                .default_message("card")
2022                .contains("credit card")
2023        );
2024        assert!(
2025            ValidationType::Phone
2026                .default_message("phone")
2027                .contains("phone")
2028        );
2029        assert!(ValidationType::Slug.default_message("url").contains("slug"));
2030        assert!(
2031            ValidationType::Hex
2032                .default_message("color")
2033                .contains("hexadecimal")
2034        );
2035        assert!(
2036            ValidationType::Base64
2037                .default_message("data")
2038                .contains("base64")
2039        );
2040        assert!(
2041            ValidationType::Json
2042                .default_message("config")
2043                .contains("JSON")
2044        );
2045        assert!(
2046            ValidationType::StartsWith("test".to_string())
2047                .default_message("field")
2048                .contains("start with")
2049        );
2050        assert!(
2051            ValidationType::EndsWith(".json".to_string())
2052                .default_message("file")
2053                .contains("end with")
2054        );
2055        assert!(
2056            ValidationType::Contains("keyword".to_string())
2057                .default_message("text")
2058                .contains("contain")
2059        );
2060        assert!(
2061            ValidationType::Length { min: 5, max: 10 }
2062                .default_message("text")
2063                .contains("between")
2064        );
2065
2066        // Numeric validators
2067        assert!(
2068            ValidationType::Negative
2069                .default_message("balance")
2070                .contains("negative")
2071        );
2072        assert!(
2073            ValidationType::NonNegative
2074                .default_message("count")
2075                .contains("not be negative")
2076        );
2077        assert!(
2078            ValidationType::NonPositive
2079                .default_message("debt")
2080                .contains("not be positive")
2081        );
2082        assert!(
2083            ValidationType::Integer
2084                .default_message("count")
2085                .contains("integer")
2086        );
2087        assert!(
2088            ValidationType::MultipleOf(5.0)
2089                .default_message("value")
2090                .contains("multiple")
2091        );
2092        assert!(
2093            ValidationType::Finite
2094                .default_message("value")
2095                .contains("finite")
2096        );
2097
2098        // Array validators
2099        assert!(
2100            ValidationType::MaxItems(10)
2101                .default_message("items")
2102                .contains("at most")
2103        );
2104        assert!(
2105            ValidationType::Items { min: 1, max: 5 }
2106                .default_message("tags")
2107                .contains("between")
2108        );
2109        assert!(
2110            ValidationType::Unique
2111                .default_message("items")
2112                .contains("unique")
2113        );
2114
2115        // Date validators
2116        assert!(
2117            ValidationType::Future
2118                .default_message("expiry")
2119                .contains("future")
2120        );
2121        assert!(
2122            ValidationType::PastOrPresent
2123                .default_message("login")
2124                .contains("not be in the future")
2125        );
2126        assert!(
2127            ValidationType::FutureOrPresent
2128                .default_message("deadline")
2129                .contains("not be in the past")
2130        );
2131        assert!(
2132            ValidationType::Before("2025-01-01".to_string())
2133                .default_message("date")
2134                .contains("before")
2135        );
2136
2137        // General validators
2138        assert!(
2139            ValidationType::Required
2140                .default_message("field")
2141                .contains("required")
2142        );
2143        assert!(
2144            ValidationType::NotEmpty
2145                .default_message("list")
2146                .contains("not be empty")
2147        );
2148        assert!(
2149            ValidationType::Custom("strongPassword".to_string())
2150                .default_message("password")
2151                .contains("custom")
2152        );
2153    }
2154
2155    #[test]
2156    fn test_validation_type_validator_names() {
2157        assert_eq!(ValidationType::Url.validator_name(), "url");
2158        assert_eq!(ValidationType::Cuid.validator_name(), "cuid");
2159        assert_eq!(ValidationType::Cuid2.validator_name(), "cuid2");
2160        assert_eq!(ValidationType::NanoId.validator_name(), "nanoid");
2161        assert_eq!(ValidationType::Ulid.validator_name(), "ulid");
2162        assert_eq!(ValidationType::Alpha.validator_name(), "alpha");
2163        assert_eq!(
2164            ValidationType::Alphanumeric.validator_name(),
2165            "alphanumeric"
2166        );
2167        assert_eq!(ValidationType::Lowercase.validator_name(), "lowercase");
2168        assert_eq!(ValidationType::Uppercase.validator_name(), "uppercase");
2169        assert_eq!(ValidationType::Trim.validator_name(), "trim");
2170        assert_eq!(
2171            ValidationType::NoWhitespace.validator_name(),
2172            "no_whitespace"
2173        );
2174        assert_eq!(ValidationType::Ip.validator_name(), "ip");
2175        assert_eq!(ValidationType::Ipv4.validator_name(), "ipv4");
2176        assert_eq!(ValidationType::Ipv6.validator_name(), "ipv6");
2177        assert_eq!(ValidationType::CreditCard.validator_name(), "credit_card");
2178        assert_eq!(ValidationType::Phone.validator_name(), "phone");
2179        assert_eq!(ValidationType::Slug.validator_name(), "slug");
2180        assert_eq!(ValidationType::Hex.validator_name(), "hex");
2181        assert_eq!(ValidationType::Base64.validator_name(), "base64");
2182        assert_eq!(ValidationType::Json.validator_name(), "json");
2183        assert_eq!(
2184            ValidationType::StartsWith("".to_string()).validator_name(),
2185            "starts_with"
2186        );
2187        assert_eq!(
2188            ValidationType::EndsWith("".to_string()).validator_name(),
2189            "ends_with"
2190        );
2191        assert_eq!(
2192            ValidationType::Contains("".to_string()).validator_name(),
2193            "contains"
2194        );
2195        assert_eq!(
2196            ValidationType::Length { min: 0, max: 0 }.validator_name(),
2197            "length"
2198        );
2199        assert_eq!(ValidationType::Max(0.0).validator_name(), "max");
2200        assert_eq!(ValidationType::Negative.validator_name(), "negative");
2201        assert_eq!(ValidationType::NonNegative.validator_name(), "non_negative");
2202        assert_eq!(ValidationType::NonPositive.validator_name(), "non_positive");
2203        assert_eq!(ValidationType::Integer.validator_name(), "integer");
2204        assert_eq!(
2205            ValidationType::MultipleOf(0.0).validator_name(),
2206            "multiple_of"
2207        );
2208        assert_eq!(ValidationType::Finite.validator_name(), "finite");
2209        assert_eq!(ValidationType::MaxItems(0).validator_name(), "max_items");
2210        assert_eq!(
2211            ValidationType::Items { min: 0, max: 0 }.validator_name(),
2212            "items"
2213        );
2214        assert_eq!(ValidationType::Unique.validator_name(), "unique");
2215        assert_eq!(ValidationType::NonEmpty.validator_name(), "non_empty");
2216        assert_eq!(ValidationType::Future.validator_name(), "future");
2217        assert_eq!(
2218            ValidationType::PastOrPresent.validator_name(),
2219            "past_or_present"
2220        );
2221        assert_eq!(
2222            ValidationType::FutureOrPresent.validator_name(),
2223            "future_or_present"
2224        );
2225        assert_eq!(
2226            ValidationType::After("".to_string()).validator_name(),
2227            "after"
2228        );
2229        assert_eq!(
2230            ValidationType::Before("".to_string()).validator_name(),
2231            "before"
2232        );
2233        assert_eq!(ValidationType::Required.validator_name(), "required");
2234        assert_eq!(ValidationType::NotEmpty.validator_name(), "not_empty");
2235        assert_eq!(ValidationType::OneOf(vec![]).validator_name(), "one_of");
2236        assert_eq!(
2237            ValidationType::Custom("".to_string()).validator_name(),
2238            "custom"
2239        );
2240    }
2241
2242    #[test]
2243    fn test_field_validation_has_rules() {
2244        let mut validation = FieldValidation::new();
2245        assert!(!validation.has_numeric_rules());
2246        assert!(!validation.has_array_rules());
2247
2248        validation.add_rule(ValidationRule::new(
2249            ValidationType::Min(0.0),
2250            Span::new(0, 0),
2251        ));
2252        assert!(validation.has_numeric_rules());
2253
2254        let mut arr_validation = FieldValidation::new();
2255        arr_validation.add_rule(ValidationRule::new(
2256            ValidationType::MinItems(1),
2257            Span::new(0, 0),
2258        ));
2259        assert!(arr_validation.has_array_rules());
2260
2261        // Date rules are checked via rule type
2262        let mut date_validation = FieldValidation::new();
2263        date_validation.add_rule(ValidationRule::new(ValidationType::Past, Span::new(0, 0)));
2264        assert!(date_validation.rules.iter().any(|r| r.is_date_rule()));
2265    }
2266
2267    #[test]
2268    fn test_parse_validation_rule_more_validators() {
2269        let span = Span::new(0, 0);
2270
2271        // String validators
2272        let url = parse_validation_rule("url", span).unwrap();
2273        assert!(matches!(url.rule_type, ValidationType::Url));
2274
2275        let cuid = parse_validation_rule("cuid", span).unwrap();
2276        assert!(matches!(cuid.rule_type, ValidationType::Cuid));
2277
2278        let cuid2 = parse_validation_rule("cuid2", span).unwrap();
2279        assert!(matches!(cuid2.rule_type, ValidationType::Cuid2));
2280
2281        let nanoid = parse_validation_rule("nanoid", span).unwrap();
2282        assert!(matches!(nanoid.rule_type, ValidationType::NanoId));
2283
2284        let ulid = parse_validation_rule("ulid", span).unwrap();
2285        assert!(matches!(ulid.rule_type, ValidationType::Ulid));
2286
2287        let alpha = parse_validation_rule("alpha", span).unwrap();
2288        assert!(matches!(alpha.rule_type, ValidationType::Alpha));
2289
2290        let alphanumeric = parse_validation_rule("alphanumeric", span).unwrap();
2291        assert!(matches!(
2292            alphanumeric.rule_type,
2293            ValidationType::Alphanumeric
2294        ));
2295
2296        let lowercase = parse_validation_rule("lowercase", span).unwrap();
2297        assert!(matches!(lowercase.rule_type, ValidationType::Lowercase));
2298
2299        let uppercase = parse_validation_rule("uppercase", span).unwrap();
2300        assert!(matches!(uppercase.rule_type, ValidationType::Uppercase));
2301
2302        let trim = parse_validation_rule("trim", span).unwrap();
2303        assert!(matches!(trim.rule_type, ValidationType::Trim));
2304
2305        let no_whitespace = parse_validation_rule("noWhitespace", span).unwrap();
2306        assert!(matches!(
2307            no_whitespace.rule_type,
2308            ValidationType::NoWhitespace
2309        ));
2310
2311        let ip = parse_validation_rule("ip", span).unwrap();
2312        assert!(matches!(ip.rule_type, ValidationType::Ip));
2313
2314        let ipv4 = parse_validation_rule("ipv4", span).unwrap();
2315        assert!(matches!(ipv4.rule_type, ValidationType::Ipv4));
2316
2317        let ipv6 = parse_validation_rule("ipv6", span).unwrap();
2318        assert!(matches!(ipv6.rule_type, ValidationType::Ipv6));
2319
2320        let credit_card = parse_validation_rule("creditCard", span).unwrap();
2321        assert!(matches!(credit_card.rule_type, ValidationType::CreditCard));
2322
2323        let phone = parse_validation_rule("phone", span).unwrap();
2324        assert!(matches!(phone.rule_type, ValidationType::Phone));
2325
2326        let slug = parse_validation_rule("slug", span).unwrap();
2327        assert!(matches!(slug.rule_type, ValidationType::Slug));
2328
2329        let hex = parse_validation_rule("hex", span).unwrap();
2330        assert!(matches!(hex.rule_type, ValidationType::Hex));
2331
2332        let base64 = parse_validation_rule("base64", span).unwrap();
2333        assert!(matches!(base64.rule_type, ValidationType::Base64));
2334
2335        let json = parse_validation_rule("json", span).unwrap();
2336        assert!(matches!(json.rule_type, ValidationType::Json));
2337
2338        // Numeric validators
2339        let negative = parse_validation_rule("negative", span).unwrap();
2340        assert!(matches!(negative.rule_type, ValidationType::Negative));
2341
2342        let non_negative = parse_validation_rule("nonNegative", span).unwrap();
2343        assert!(matches!(
2344            non_negative.rule_type,
2345            ValidationType::NonNegative
2346        ));
2347
2348        let non_positive = parse_validation_rule("nonPositive", span).unwrap();
2349        assert!(matches!(
2350            non_positive.rule_type,
2351            ValidationType::NonPositive
2352        ));
2353
2354        let integer = parse_validation_rule("integer", span).unwrap();
2355        assert!(matches!(integer.rule_type, ValidationType::Integer));
2356
2357        let finite = parse_validation_rule("finite", span).unwrap();
2358        assert!(matches!(finite.rule_type, ValidationType::Finite));
2359
2360        // Array validators
2361        let unique = parse_validation_rule("unique", span).unwrap();
2362        assert!(matches!(unique.rule_type, ValidationType::Unique));
2363
2364        let non_empty = parse_validation_rule("nonEmpty", span).unwrap();
2365        assert!(matches!(non_empty.rule_type, ValidationType::NonEmpty));
2366
2367        // Date validators
2368        let past = parse_validation_rule("past", span).unwrap();
2369        assert!(matches!(past.rule_type, ValidationType::Past));
2370
2371        let future = parse_validation_rule("future", span).unwrap();
2372        assert!(matches!(future.rule_type, ValidationType::Future));
2373
2374        let past_or_present = parse_validation_rule("pastOrPresent", span).unwrap();
2375        assert!(matches!(
2376            past_or_present.rule_type,
2377            ValidationType::PastOrPresent
2378        ));
2379
2380        let future_or_present = parse_validation_rule("futureOrPresent", span).unwrap();
2381        assert!(matches!(
2382            future_or_present.rule_type,
2383            ValidationType::FutureOrPresent
2384        ));
2385
2386        // General validators
2387        let required = parse_validation_rule("required", span).unwrap();
2388        assert!(matches!(required.rule_type, ValidationType::Required));
2389
2390        let not_empty = parse_validation_rule("notEmpty", span).unwrap();
2391        assert!(matches!(not_empty.rule_type, ValidationType::NotEmpty));
2392    }
2393
2394    #[test]
2395    fn test_parse_validation_rule_with_string_args() {
2396        let span = Span::new(0, 0);
2397
2398        let starts_with = parse_validation_rule(r#"startsWith("PREFIX_")"#, span).unwrap();
2399        if let ValidationType::StartsWith(prefix) = starts_with.rule_type {
2400            assert_eq!(prefix, "PREFIX_");
2401        } else {
2402            panic!("Expected StartsWith");
2403        }
2404
2405        let ends_with = parse_validation_rule(r#"endsWith(".json")"#, span).unwrap();
2406        if let ValidationType::EndsWith(suffix) = ends_with.rule_type {
2407            assert_eq!(suffix, ".json");
2408        } else {
2409            panic!("Expected EndsWith");
2410        }
2411
2412        let contains = parse_validation_rule(r#"contains("keyword")"#, span).unwrap();
2413        if let ValidationType::Contains(substring) = contains.rule_type {
2414            assert_eq!(substring, "keyword");
2415        } else {
2416            panic!("Expected Contains");
2417        }
2418
2419        let custom = parse_validation_rule(r#"custom("myValidator")"#, span).unwrap();
2420        if let ValidationType::Custom(name) = custom.rule_type {
2421            assert_eq!(name, "myValidator");
2422        } else {
2423            panic!("Expected Custom");
2424        }
2425
2426        let after = parse_validation_rule(r#"after("2024-01-01")"#, span).unwrap();
2427        if let ValidationType::After(date) = after.rule_type {
2428            assert_eq!(date, "2024-01-01");
2429        } else {
2430            panic!("Expected After");
2431        }
2432
2433        let before = parse_validation_rule(r#"before("2025-12-31")"#, span).unwrap();
2434        if let ValidationType::Before(date) = before.rule_type {
2435            assert_eq!(date, "2025-12-31");
2436        } else {
2437            panic!("Expected Before");
2438        }
2439    }
2440
2441    #[test]
2442    fn test_parse_validation_rule_numeric_args() {
2443        let span = Span::new(0, 0);
2444
2445        let min = parse_validation_rule("min(10)", span).unwrap();
2446        if let ValidationType::Min(n) = min.rule_type {
2447            assert!((n - 10.0).abs() < f64::EPSILON);
2448        } else {
2449            panic!("Expected Min");
2450        }
2451
2452        let max = parse_validation_rule("max(100)", span).unwrap();
2453        if let ValidationType::Max(n) = max.rule_type {
2454            assert!((n - 100.0).abs() < f64::EPSILON);
2455        } else {
2456            panic!("Expected Max");
2457        }
2458
2459        let multiple_of = parse_validation_rule("multipleOf(5)", span).unwrap();
2460        if let ValidationType::MultipleOf(n) = multiple_of.rule_type {
2461            assert!((n - 5.0).abs() < f64::EPSILON);
2462        } else {
2463            panic!("Expected MultipleOf");
2464        }
2465
2466        let min_items = parse_validation_rule("minItems(1)", span).unwrap();
2467        assert!(matches!(min_items.rule_type, ValidationType::MinItems(1)));
2468
2469        let max_items = parse_validation_rule("maxItems(10)", span).unwrap();
2470        assert!(matches!(max_items.rule_type, ValidationType::MaxItems(10)));
2471
2472        let length = parse_validation_rule("length(5, 100)", span).unwrap();
2473        if let ValidationType::Length { min, max } = length.rule_type {
2474            assert_eq!(min, 5);
2475            assert_eq!(max, 100);
2476        } else {
2477            panic!("Expected Length");
2478        }
2479
2480        let items = parse_validation_rule("items(1, 10)", span).unwrap();
2481        if let ValidationType::Items { min, max } = items.rule_type {
2482            assert_eq!(min, 1);
2483            assert_eq!(max, 10);
2484        } else {
2485            panic!("Expected Items");
2486        }
2487    }
2488
2489    #[test]
2490    fn test_parse_validation_rule_unknown() {
2491        let span = Span::new(0, 0);
2492        assert!(parse_validation_rule("unknownValidator", span).is_none());
2493    }
2494
2495    #[test]
2496    fn test_field_metadata_more_tags() {
2497        let span = Span::new(0, 0);
2498        let tags = vec![
2499            DocTag::new("internal", None, span),
2500            DocTag::new(
2501                "description",
2502                Some("A detailed description".to_string()),
2503                span,
2504            ),
2505            DocTag::new("seeAlso", Some("otherField".to_string()), span),
2506            DocTag::new("omitFromInput", None, span),
2507            DocTag::new("omitFromOutput", None, span),
2508        ];
2509
2510        let meta = FieldMetadata::from_tags(&tags);
2511
2512        assert!(meta.internal);
2513        assert_eq!(meta.description, Some("A detailed description".to_string()));
2514        assert_eq!(meta.see_also, vec!["otherField".to_string()]);
2515        assert!(meta.omit_from_input);
2516        assert!(meta.omit_from_output);
2517    }
2518
2519    #[test]
2520    fn test_field_permissions_none() {
2521        let perms = FieldPermissions::none();
2522        assert!(!perms.read);
2523        assert!(!perms.create);
2524        assert!(!perms.update);
2525        assert!(!perms.filter);
2526        assert!(!perms.sort);
2527    }
2528
2529    #[test]
2530    fn test_enhanced_documentation_no_validation() {
2531        let raw = "Just a simple description";
2532        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2533
2534        assert_eq!(doc.text, "Just a simple description");
2535        assert!(!doc.has_validation());
2536        assert_eq!(doc.validation.len(), 0);
2537        assert!(doc.tags.is_empty());
2538    }
2539
2540    #[test]
2541    fn test_enhanced_documentation_readonly() {
2542        let raw = r#"ID field
2543@readonly"#;
2544
2545        let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2546
2547        assert!(doc.is_readonly());
2548        assert!(!doc.is_hidden());
2549        assert!(!doc.is_sensitive());
2550    }
2551}