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