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!(
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 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 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 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 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 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 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
540pub enum ValidationValue {
541 String(String),
543 Int(i64),
545 Float(f64),
547 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#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
564pub struct FieldValidation {
565 pub rules: Vec<ValidationRule>,
567}
568
569impl FieldValidation {
570 pub fn new() -> Self {
572 Self { rules: Vec::new() }
573 }
574
575 pub fn add_rule(&mut self, rule: ValidationRule) {
577 self.rules.push(rule);
578 }
579
580 pub fn is_empty(&self) -> bool {
582 self.rules.is_empty()
583 }
584
585 pub fn len(&self) -> usize {
587 self.rules.len()
588 }
589
590 pub fn has_string_rules(&self) -> bool {
592 self.rules.iter().any(|r| r.is_string_rule())
593 }
594
595 pub fn has_numeric_rules(&self) -> bool {
597 self.rules.iter().any(|r| r.is_numeric_rule())
598 }
599
600 pub fn has_array_rules(&self) -> bool {
602 self.rules.iter().any(|r| r.is_array_rule())
603 }
604
605 pub fn is_required(&self) -> bool {
607 self.rules
608 .iter()
609 .any(|r| matches!(r.rule_type, ValidationType::Required))
610 }
611}
612
613#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
615pub struct EnhancedDocumentation {
616 pub text: String,
618 pub validation: FieldValidation,
620 pub tags: Vec<DocTag>,
622 pub span: Span,
624}
625
626impl EnhancedDocumentation {
627 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 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 if let Some(validate_content) = trimmed.strip_prefix("@validate:") {
648 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 else if let Some(tag) = parse_doc_tag(trimmed, span) {
657 tags.push(tag);
658 }
659 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 pub fn has_validation(&self) -> bool {
675 !self.validation.is_empty()
676 }
677
678 pub fn validation_rules(&self) -> &[ValidationRule] {
680 &self.validation.rules
681 }
682
683 pub fn get_tag(&self, name: &str) -> Option<&DocTag> {
685 self.tags.iter().find(|t| t.name == name)
686 }
687
688 pub fn get_tags(&self, name: &str) -> Vec<&DocTag> {
690 self.tags.iter().filter(|t| t.name == name).collect()
691 }
692
693 pub fn has_tag(&self, name: &str) -> bool {
695 self.tags.iter().any(|t| t.name == name)
696 }
697
698 pub fn extract_metadata(&self) -> FieldMetadata {
700 FieldMetadata::from_tags(&self.tags)
701 }
702
703 pub fn is_hidden(&self) -> bool {
705 self.has_tag("hidden") || self.has_tag("internal")
706 }
707
708 pub fn is_deprecated(&self) -> bool {
710 self.has_tag("deprecated")
711 }
712
713 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 pub fn is_sensitive(&self) -> bool {
726 self.has_tag("sensitive") || self.has_tag("writeonly")
727 }
728
729 pub fn is_readonly(&self) -> bool {
731 self.has_tag("readonly") || self.has_tag("readOnly")
732 }
733
734 pub fn is_writeonly(&self) -> bool {
736 self.has_tag("writeonly") || self.has_tag("writeOnly")
737 }
738
739 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 pub fn label(&self) -> Option<&str> {
750 self.get_tag("label").and_then(|t| t.value.as_deref())
751 }
752
753 pub fn placeholder(&self) -> Option<&str> {
755 self.get_tag("placeholder").and_then(|t| t.value.as_deref())
756 }
757
758 pub fn since(&self) -> Option<&str> {
760 self.get_tag("since").and_then(|t| t.value.as_deref())
761 }
762
763 pub fn group(&self) -> Option<&str> {
765 self.get_tag("group").and_then(|t| t.value.as_deref())
766 }
767}
768
769#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
771pub struct DocTag {
772 pub name: SmolStr,
774 pub value: Option<String>,
776 pub span: Span,
778}
779
780impl DocTag {
781 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#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
822pub struct FieldMetadata {
823 pub hidden: bool,
826 pub internal: bool,
828 pub sensitive: bool,
830
831 pub readonly: bool,
834 pub writeonly: bool,
836 pub input_only: bool,
838 pub output_only: bool,
840 pub omit_from_output: bool,
842 pub omit_from_input: bool,
844
845 pub deprecated: Option<DeprecationInfo>,
848
849 pub label: Option<String>,
852 pub description: Option<String>,
854 pub placeholder: Option<String>,
856 pub examples: Vec<String>,
858 pub see_also: Vec<String>,
860 pub since: Option<String>,
862
863 pub alias: Option<String>,
866 pub serialized_name: Option<String>,
868 pub order: Option<i32>,
870 pub default_value: Option<String>,
872
873 pub group: Option<String>,
876 pub format: Option<String>,
878 pub input_type: Option<String>,
880 pub max_width: Option<u32>,
882 pub multiline: bool,
884 pub rich_text: bool,
886}
887
888impl FieldMetadata {
889 pub fn new() -> Self {
891 Self::default()
892 }
893
894 pub fn is_hidden(&self) -> bool {
896 self.hidden || self.internal
897 }
898
899 pub fn should_omit_from_output(&self) -> bool {
901 self.omit_from_output || self.writeonly || self.hidden
902 }
903
904 pub fn should_omit_from_input(&self) -> bool {
906 self.omit_from_input || self.readonly || self.output_only
907 }
908
909 pub fn is_deprecated(&self) -> bool {
911 self.deprecated.is_some()
912 }
913
914 pub fn deprecation_message(&self) -> Option<&str> {
916 self.deprecated.as_ref().map(|d| d.message.as_str())
917 }
918
919 pub fn is_sensitive(&self) -> bool {
921 self.sensitive || self.writeonly
922 }
923
924 pub fn display_label(&self) -> Option<&str> {
926 self.label.as_deref()
927 }
928
929 pub fn get_examples(&self) -> &[String] {
931 &self.examples
932 }
933
934 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 "hidden" => meta.hidden = true,
942 "internal" => meta.internal = true,
943 "sensitive" => meta.sensitive = true,
944
945 "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 "deprecated" => {
955 meta.deprecated = Some(DeprecationInfo {
956 message: tag.value.clone().unwrap_or_default(),
957 since: None,
958 replacement: None,
959 });
960 }
961
962 "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 "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 "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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1012pub struct DeprecationInfo {
1013 pub message: String,
1015 pub since: Option<String>,
1017 pub replacement: Option<String>,
1019}
1020
1021impl DeprecationInfo {
1022 pub fn new(message: impl Into<String>) -> Self {
1024 Self {
1025 message: message.into(),
1026 since: None,
1027 replacement: None,
1028 }
1029 }
1030
1031 pub fn since(mut self, version: impl Into<String>) -> Self {
1033 self.since = Some(version.into());
1034 self
1035 }
1036
1037 pub fn replacement(mut self, field: impl Into<String>) -> Self {
1039 self.replacement = Some(field.into());
1040 self
1041 }
1042
1043 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1058pub enum Visibility {
1059 #[default]
1061 Public,
1062 Internal,
1064 Hidden,
1066 Private,
1068}
1069
1070impl Visibility {
1071 pub fn is_public(&self) -> bool {
1073 matches!(self, Self::Public)
1074 }
1075
1076 pub fn is_admin_visible(&self) -> bool {
1078 matches!(self, Self::Public | Self::Internal)
1079 }
1080
1081 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1106pub struct FieldPermissions {
1107 pub read: bool,
1109 pub create: bool,
1111 pub update: bool,
1113 pub filter: bool,
1115 pub sort: bool,
1117}
1118
1119impl FieldPermissions {
1120 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 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 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 pub fn none() -> Self {
1155 Self::default()
1156 }
1157
1158 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
1174fn 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 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 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
1327fn parse_one_of_values(s: &str) -> Vec<ValidationValue> {
1329 let mut values = Vec::new();
1330
1331 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 if let Some(val) = parse_validation_value(current.trim()) {
1359 values.push(val);
1360 }
1361
1362 values
1363}
1364
1365fn parse_validation_value(s: &str) -> Option<ValidationValue> {
1367 let s = s.trim();
1368 if s.is_empty() {
1369 return None;
1370 }
1371
1372 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 if s == "true" {
1380 return Some(ValidationValue::Bool(true));
1381 }
1382 if s == "false" {
1383 return Some(ValidationValue::Bool(false));
1384 }
1385
1386 if let Ok(i) = s.parse::<i64>() {
1388 return Some(ValidationValue::Int(i));
1389 }
1390
1391 if let Ok(f) = s.parse::<f64>() {
1393 return Some(ValidationValue::Float(f));
1394 }
1395
1396 Some(ValidationValue::String(s.to_string()))
1398}
1399
1400fn 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..]; let (name, value) = if let Some(space_idx) = content.find(char::is_whitespace) {
1408 (
1409 &content[..space_idx],
1410 Some(content[space_idx..].trim().to_string()),
1411 )
1412 } else {
1413 (content, None)
1414 };
1415
1416 Some(DocTag::new(name, value, span))
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421 use super::*;
1422
1423 #[test]
1424 fn test_validation_rule_new() {
1425 let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1426 assert!(matches!(rule.rule_type, ValidationType::Email));
1427 assert!(rule.message.is_none());
1428 }
1429
1430 #[test]
1431 fn test_validation_rule_with_message() {
1432 let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1433 .with_message("Please enter a valid email");
1434 assert_eq!(rule.message, Some("Please enter a valid email".to_string()));
1435 }
1436
1437 #[test]
1438 fn test_validation_type_default_messages() {
1439 let email_msg = ValidationType::Email.default_message("email");
1440 assert!(email_msg.contains("email"));
1441 assert!(email_msg.contains("valid"));
1442
1443 let min_msg = ValidationType::Min(10.0).default_message("age");
1444 assert!(min_msg.contains("age"));
1445 assert!(min_msg.contains("10"));
1446 }
1447
1448 #[test]
1449 fn test_validation_type_is_string_rule() {
1450 assert!(ValidationType::Email.is_string_rule());
1451 assert!(ValidationType::Regex(".*".to_string()).is_string_rule());
1452 assert!(!ValidationType::Min(0.0).is_string_rule());
1453 }
1454
1455 #[test]
1456 fn test_validation_type_is_numeric_rule() {
1457 assert!(ValidationType::Min(0.0).is_numeric_rule());
1458 assert!(ValidationType::Positive.is_numeric_rule());
1459 assert!(!ValidationType::Email.is_numeric_rule());
1460 }
1461
1462 #[test]
1463 fn test_validation_type_is_array_rule() {
1464 assert!(ValidationType::MinItems(1).is_array_rule());
1465 assert!(ValidationType::Unique.is_array_rule());
1466 assert!(!ValidationType::Email.is_array_rule());
1467 }
1468
1469 #[test]
1470 fn test_validation_type_is_date_rule() {
1471 assert!(ValidationType::Past.is_date_rule());
1472 assert!(ValidationType::After("2024-01-01".to_string()).is_date_rule());
1473 assert!(!ValidationType::Email.is_date_rule());
1474 }
1475
1476 #[test]
1477 fn test_field_validation() {
1478 let mut validation = FieldValidation::new();
1479 assert!(validation.is_empty());
1480
1481 validation.add_rule(ValidationRule::new(ValidationType::Email, Span::new(0, 0)));
1482 validation.add_rule(ValidationRule::new(
1483 ValidationType::MaxLength(255),
1484 Span::new(0, 0),
1485 ));
1486
1487 assert_eq!(validation.len(), 2);
1488 assert!(!validation.is_empty());
1489 assert!(validation.has_string_rules());
1490 }
1491
1492 #[test]
1493 fn test_field_validation_is_required() {
1494 let mut validation = FieldValidation::new();
1495 assert!(!validation.is_required());
1496
1497 validation.add_rule(ValidationRule::new(
1498 ValidationType::Required,
1499 Span::new(0, 0),
1500 ));
1501 assert!(validation.is_required());
1502 }
1503
1504 #[test]
1505 fn test_parse_validation_rule_simple() {
1506 let span = Span::new(0, 0);
1507
1508 let email = parse_validation_rule("email", span).unwrap();
1509 assert!(matches!(email.rule_type, ValidationType::Email));
1510
1511 let uuid = parse_validation_rule("uuid", span).unwrap();
1512 assert!(matches!(uuid.rule_type, ValidationType::Uuid));
1513
1514 let positive = parse_validation_rule("positive", span).unwrap();
1515 assert!(matches!(positive.rule_type, ValidationType::Positive));
1516 }
1517
1518 #[test]
1519 fn test_parse_validation_rule_with_args() {
1520 let span = Span::new(0, 0);
1521
1522 let min_length = parse_validation_rule("minLength(5)", span).unwrap();
1523 assert!(matches!(min_length.rule_type, ValidationType::MinLength(5)));
1524
1525 let max = parse_validation_rule("max(100)", span).unwrap();
1526 assert!(
1527 matches!(max.rule_type, ValidationType::Max(n) if (n - 100.0).abs() < f64::EPSILON)
1528 );
1529
1530 let range = parse_validation_rule("range(0, 100)", span).unwrap();
1531 if let ValidationType::Range { min, max } = range.rule_type {
1532 assert!((min - 0.0).abs() < f64::EPSILON);
1533 assert!((max - 100.0).abs() < f64::EPSILON);
1534 } else {
1535 panic!("Expected Range");
1536 }
1537 }
1538
1539 #[test]
1540 fn test_parse_validation_rule_regex() {
1541 let span = Span::new(0, 0);
1542
1543 let regex = parse_validation_rule(r#"regex("^[a-z]+$")"#, span).unwrap();
1544 if let ValidationType::Regex(pattern) = regex.rule_type {
1545 assert_eq!(pattern, "^[a-z]+$");
1546 } else {
1547 panic!("Expected Regex");
1548 }
1549 }
1550
1551 #[test]
1552 fn test_parse_validation_rule_one_of() {
1553 let span = Span::new(0, 0);
1554
1555 let one_of = parse_validation_rule(r#"oneOf("a", "b", "c")"#, span).unwrap();
1556 if let ValidationType::OneOf(values) = one_of.rule_type {
1557 assert_eq!(values.len(), 3);
1558 assert_eq!(values[0], ValidationValue::String("a".to_string()));
1559 } else {
1560 panic!("Expected OneOf");
1561 }
1562 }
1563
1564 #[test]
1565 fn test_parse_validation_value() {
1566 assert_eq!(
1567 parse_validation_value("\"hello\""),
1568 Some(ValidationValue::String("hello".to_string()))
1569 );
1570 assert_eq!(parse_validation_value("42"), Some(ValidationValue::Int(42)));
1571 assert_eq!(
1572 parse_validation_value("3.14"),
1573 Some(ValidationValue::Float(3.14))
1574 );
1575 assert_eq!(
1576 parse_validation_value("true"),
1577 Some(ValidationValue::Bool(true))
1578 );
1579 }
1580
1581 #[test]
1582 fn test_enhanced_documentation_parse() {
1583 let raw = r#"The user's email address
1584@validate: email, maxLength(255)
1585@deprecated Use newEmail instead"#;
1586
1587 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1588
1589 assert_eq!(doc.text, "The user's email address");
1590 assert!(doc.has_validation());
1591 assert_eq!(doc.validation.len(), 2);
1592 assert_eq!(doc.tags.len(), 1);
1593 assert_eq!(doc.tags[0].name.as_str(), "deprecated");
1594 }
1595
1596 #[test]
1597 fn test_enhanced_documentation_multiple_validate_lines() {
1598 let raw = r#"Username must be valid
1599@validate: minLength(3), maxLength(30)
1600@validate: regex("^[a-z0-9_]+$")"#;
1601
1602 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1603
1604 assert_eq!(doc.text, "Username must be valid");
1605 assert_eq!(doc.validation.len(), 3);
1606 }
1607
1608 #[test]
1609 fn test_doc_tag_parsing() {
1610 let span = Span::new(0, 0);
1611
1612 let tag = parse_doc_tag("@deprecated Use newField instead", span).unwrap();
1613 assert_eq!(tag.name.as_str(), "deprecated");
1614 assert_eq!(tag.value, Some("Use newField instead".to_string()));
1615
1616 let tag_no_value = parse_doc_tag("@internal", span).unwrap();
1617 assert_eq!(tag_no_value.name.as_str(), "internal");
1618 assert!(tag_no_value.value.is_none());
1619 }
1620
1621 #[test]
1622 fn test_validation_value_display() {
1623 assert_eq!(
1624 format!("{}", ValidationValue::String("test".to_string())),
1625 "\"test\""
1626 );
1627 assert_eq!(format!("{}", ValidationValue::Int(42)), "42");
1628 assert_eq!(format!("{}", ValidationValue::Float(3.14)), "3.14");
1629 assert_eq!(format!("{}", ValidationValue::Bool(true)), "true");
1630 }
1631
1632 #[test]
1633 fn test_validator_name() {
1634 assert_eq!(ValidationType::Email.validator_name(), "email");
1635 assert_eq!(ValidationType::MinLength(5).validator_name(), "min_length");
1636 assert_eq!(
1637 ValidationType::Range {
1638 min: 0.0,
1639 max: 100.0
1640 }
1641 .validator_name(),
1642 "range"
1643 );
1644 }
1645
1646 #[test]
1649 fn test_field_metadata_default() {
1650 let meta = FieldMetadata::new();
1651 assert!(!meta.hidden);
1652 assert!(!meta.internal);
1653 assert!(!meta.sensitive);
1654 assert!(!meta.readonly);
1655 assert!(!meta.writeonly);
1656 assert!(meta.deprecated.is_none());
1657 assert!(meta.label.is_none());
1658 assert!(meta.examples.is_empty());
1659 }
1660
1661 #[test]
1662 fn test_field_metadata_from_tags() {
1663 let span = Span::new(0, 0);
1664 let tags = vec![
1665 DocTag::new("hidden", None, span),
1666 DocTag::new("sensitive", None, span),
1667 DocTag::new("label", Some("User ID".to_string()), span),
1668 DocTag::new("example", Some("12345".to_string()), span),
1669 DocTag::new("example", Some("67890".to_string()), span),
1670 ];
1671
1672 let meta = FieldMetadata::from_tags(&tags);
1673
1674 assert!(meta.hidden);
1675 assert!(meta.sensitive);
1676 assert_eq!(meta.label, Some("User ID".to_string()));
1677 assert_eq!(meta.examples.len(), 2);
1678 assert_eq!(meta.examples[0], "12345");
1679 assert_eq!(meta.examples[1], "67890");
1680 }
1681
1682 #[test]
1683 fn test_field_metadata_deprecated() {
1684 let span = Span::new(0, 0);
1685 let tags = vec![DocTag::new(
1686 "deprecated",
1687 Some("Use newField instead".to_string()),
1688 span,
1689 )];
1690
1691 let meta = FieldMetadata::from_tags(&tags);
1692
1693 assert!(meta.is_deprecated());
1694 assert_eq!(meta.deprecation_message(), Some("Use newField instead"));
1695 }
1696
1697 #[test]
1698 fn test_field_metadata_readonly_writeonly() {
1699 let span = Span::new(0, 0);
1700
1701 let readonly_tags = vec![DocTag::new("readonly", None, span)];
1702 let readonly_meta = FieldMetadata::from_tags(&readonly_tags);
1703 assert!(readonly_meta.readonly);
1704 assert!(readonly_meta.should_omit_from_input());
1705 assert!(!readonly_meta.should_omit_from_output());
1706
1707 let writeonly_tags = vec![DocTag::new("writeonly", None, span)];
1708 let writeonly_meta = FieldMetadata::from_tags(&writeonly_tags);
1709 assert!(writeonly_meta.writeonly);
1710 assert!(writeonly_meta.should_omit_from_output());
1711 assert!(!writeonly_meta.should_omit_from_input());
1712 }
1713
1714 #[test]
1715 fn test_field_metadata_serialization() {
1716 let span = Span::new(0, 0);
1717 let tags = vec![
1718 DocTag::new("alias", Some("userId".to_string()), span),
1719 DocTag::new("serializedName", Some("user_id".to_string()), span),
1720 DocTag::new("order", Some("1".to_string()), span),
1721 ];
1722
1723 let meta = FieldMetadata::from_tags(&tags);
1724
1725 assert_eq!(meta.alias, Some("userId".to_string()));
1726 assert_eq!(meta.serialized_name, Some("user_id".to_string()));
1727 assert_eq!(meta.order, Some(1));
1728 }
1729
1730 #[test]
1731 fn test_field_metadata_ui_hints() {
1732 let span = Span::new(0, 0);
1733 let tags = vec![
1734 DocTag::new("group", Some("Personal Info".to_string()), span),
1735 DocTag::new("format", Some("date".to_string()), span),
1736 DocTag::new("inputType", Some("textarea".to_string()), span),
1737 DocTag::new("multiline", None, span),
1738 DocTag::new("maxWidth", Some("500".to_string()), span),
1739 ];
1740
1741 let meta = FieldMetadata::from_tags(&tags);
1742
1743 assert_eq!(meta.group, Some("Personal Info".to_string()));
1744 assert_eq!(meta.format, Some("date".to_string()));
1745 assert_eq!(meta.input_type, Some("textarea".to_string()));
1746 assert!(meta.multiline);
1747 assert_eq!(meta.max_width, Some(500));
1748 }
1749
1750 #[test]
1751 fn test_deprecation_info() {
1752 let info = DeprecationInfo::new("Field is deprecated")
1753 .since("2.0.0")
1754 .replacement("newField");
1755
1756 assert_eq!(info.message, "Field is deprecated");
1757 assert_eq!(info.since, Some("2.0.0".to_string()));
1758 assert_eq!(info.replacement, Some("newField".to_string()));
1759
1760 let formatted = info.format_message();
1761 assert!(formatted.contains("Field is deprecated"));
1762 assert!(formatted.contains("since 2.0.0"));
1763 assert!(formatted.contains("Use newField instead"));
1764 }
1765
1766 #[test]
1767 fn test_visibility_levels() {
1768 assert!(Visibility::Public.is_public());
1769 assert!(Visibility::Public.is_admin_visible());
1770
1771 assert!(!Visibility::Internal.is_public());
1772 assert!(Visibility::Internal.is_admin_visible());
1773
1774 assert!(!Visibility::Hidden.is_public());
1775 assert!(!Visibility::Hidden.is_admin_visible());
1776
1777 assert!(!Visibility::Private.is_public());
1778 assert!(!Visibility::Private.is_admin_visible());
1779 }
1780
1781 #[test]
1782 fn test_visibility_from_str() {
1783 assert_eq!(Visibility::parse("public"), Some(Visibility::Public));
1784 assert_eq!(Visibility::parse("INTERNAL"), Some(Visibility::Internal));
1785 assert_eq!(Visibility::parse("Hidden"), Some(Visibility::Hidden));
1786 assert_eq!(Visibility::parse("private"), Some(Visibility::Private));
1787 assert_eq!(Visibility::parse("unknown"), None);
1788 }
1789
1790 #[test]
1791 fn test_field_permissions_all() {
1792 let perms = FieldPermissions::all();
1793 assert!(perms.read);
1794 assert!(perms.create);
1795 assert!(perms.update);
1796 assert!(perms.filter);
1797 assert!(perms.sort);
1798 }
1799
1800 #[test]
1801 fn test_field_permissions_readonly() {
1802 let perms = FieldPermissions::readonly();
1803 assert!(perms.read);
1804 assert!(!perms.create);
1805 assert!(!perms.update);
1806 assert!(perms.filter);
1807 assert!(perms.sort);
1808 }
1809
1810 #[test]
1811 fn test_field_permissions_writeonly() {
1812 let perms = FieldPermissions::writeonly();
1813 assert!(!perms.read);
1814 assert!(perms.create);
1815 assert!(perms.update);
1816 assert!(!perms.filter);
1817 assert!(!perms.sort);
1818 }
1819
1820 #[test]
1821 fn test_field_permissions_from_metadata() {
1822 let mut meta = FieldMetadata::new();
1823 meta.readonly = true;
1824
1825 let perms = FieldPermissions::from_metadata(&meta);
1826 assert!(perms.read);
1827 assert!(!perms.create);
1828 assert!(!perms.update);
1829
1830 let mut sensitive_meta = FieldMetadata::new();
1831 sensitive_meta.sensitive = true;
1832
1833 let sensitive_perms = FieldPermissions::from_metadata(&sensitive_meta);
1834 assert!(sensitive_perms.read);
1835 assert!(!sensitive_perms.filter);
1836 assert!(!sensitive_perms.sort);
1837 }
1838
1839 #[test]
1840 fn test_enhanced_documentation_metadata_extraction() {
1841 let raw = r#"User's password hash
1842@hidden
1843@sensitive
1844@writeonly
1845@label Password
1846@since 1.0.0"#;
1847
1848 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1849
1850 assert!(doc.is_hidden());
1851 assert!(doc.is_sensitive());
1852 assert!(doc.is_writeonly());
1853 assert_eq!(doc.label(), Some("Password"));
1854 assert_eq!(doc.since(), Some("1.0.0"));
1855
1856 let meta = doc.extract_metadata();
1857 assert!(meta.hidden);
1858 assert!(meta.sensitive);
1859 assert!(meta.writeonly);
1860 }
1861
1862 #[test]
1863 fn test_enhanced_documentation_examples() {
1864 let raw = r#"Email address
1865@example user@example.com
1866@example admin@company.org
1867@placeholder Enter your email"#;
1868
1869 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1870
1871 let examples = doc.examples();
1872 assert_eq!(examples.len(), 2);
1873 assert_eq!(examples[0], "user@example.com");
1874 assert_eq!(examples[1], "admin@company.org");
1875 assert_eq!(doc.placeholder(), Some("Enter your email"));
1876 }
1877
1878 #[test]
1879 fn test_enhanced_documentation_deprecation() {
1880 let raw = r#"Old email field
1881@deprecated Use newEmail instead
1882@since 1.0.0"#;
1883
1884 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1885
1886 assert!(doc.is_deprecated());
1887 let info = doc.deprecation_info().unwrap();
1888 assert_eq!(info.message, "Use newEmail instead");
1889 assert_eq!(info.since, Some("1.0.0".to_string()));
1890 }
1891
1892 #[test]
1893 fn test_enhanced_documentation_group() {
1894 let raw = r#"User's display name
1895@group Personal Information
1896@format text"#;
1897
1898 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
1899
1900 assert_eq!(doc.group(), Some("Personal Information"));
1901 let meta = doc.extract_metadata();
1902 assert_eq!(meta.format, Some("text".to_string()));
1903 }
1904
1905 #[test]
1908 fn test_validation_rule_error_message_custom() {
1909 let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0))
1910 .with_message("Please provide a valid email");
1911 assert_eq!(rule.error_message("email"), "Please provide a valid email");
1912 }
1913
1914 #[test]
1915 fn test_validation_rule_error_message_default() {
1916 let rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1917 let msg = rule.error_message("email");
1918 assert!(msg.contains("email"));
1919 }
1920
1921 #[test]
1922 fn test_validation_rule_type_checks() {
1923 let email_rule = ValidationRule::new(ValidationType::Email, Span::new(0, 0));
1924 assert!(email_rule.is_string_rule());
1925 assert!(!email_rule.is_numeric_rule());
1926 assert!(!email_rule.is_array_rule());
1927 assert!(!email_rule.is_date_rule());
1928
1929 let min_rule = ValidationRule::new(ValidationType::Min(0.0), Span::new(0, 0));
1930 assert!(!min_rule.is_string_rule());
1931 assert!(min_rule.is_numeric_rule());
1932 assert!(!min_rule.is_array_rule());
1933
1934 let items_rule = ValidationRule::new(ValidationType::MinItems(1), Span::new(0, 0));
1935 assert!(!items_rule.is_string_rule());
1936 assert!(!items_rule.is_numeric_rule());
1937 assert!(items_rule.is_array_rule());
1938
1939 let past_rule = ValidationRule::new(ValidationType::Past, Span::new(0, 0));
1940 assert!(!past_rule.is_string_rule());
1941 assert!(!past_rule.is_numeric_rule());
1942 assert!(!past_rule.is_array_rule());
1943 assert!(past_rule.is_date_rule());
1944 }
1945
1946 #[test]
1947 fn test_validation_type_is_id_format_rule() {
1948 assert!(ValidationType::Uuid.is_id_format_rule());
1949 assert!(ValidationType::Cuid.is_id_format_rule());
1950 assert!(ValidationType::Cuid2.is_id_format_rule());
1951 assert!(ValidationType::NanoId.is_id_format_rule());
1952 assert!(ValidationType::Ulid.is_id_format_rule());
1953 assert!(!ValidationType::Email.is_id_format_rule());
1954 }
1955
1956 #[test]
1957 fn test_validation_type_default_messages_comprehensive() {
1958 assert!(
1960 ValidationType::Url
1961 .default_message("website")
1962 .contains("URL")
1963 );
1964 assert!(ValidationType::Cuid.default_message("id").contains("CUID"));
1965 assert!(
1966 ValidationType::Cuid2
1967 .default_message("id")
1968 .contains("CUID2")
1969 );
1970 assert!(
1971 ValidationType::NanoId
1972 .default_message("id")
1973 .contains("NanoId")
1974 );
1975 assert!(ValidationType::Ulid.default_message("id").contains("ULID"));
1976 assert!(
1977 ValidationType::Alpha
1978 .default_message("name")
1979 .contains("letters")
1980 );
1981 assert!(
1982 ValidationType::Alphanumeric
1983 .default_message("code")
1984 .contains("letters and numbers")
1985 );
1986 assert!(
1987 ValidationType::Lowercase
1988 .default_message("slug")
1989 .contains("lowercase")
1990 );
1991 assert!(
1992 ValidationType::Uppercase
1993 .default_message("code")
1994 .contains("uppercase")
1995 );
1996 assert!(
1997 ValidationType::Trim
1998 .default_message("text")
1999 .contains("whitespace")
2000 );
2001 assert!(
2002 ValidationType::NoWhitespace
2003 .default_message("username")
2004 .contains("whitespace")
2005 );
2006 assert!(ValidationType::Ip.default_message("address").contains("IP"));
2007 assert!(
2008 ValidationType::Ipv4
2009 .default_message("address")
2010 .contains("IPv4")
2011 );
2012 assert!(
2013 ValidationType::Ipv6
2014 .default_message("address")
2015 .contains("IPv6")
2016 );
2017 assert!(
2018 ValidationType::CreditCard
2019 .default_message("card")
2020 .contains("credit card")
2021 );
2022 assert!(
2023 ValidationType::Phone
2024 .default_message("phone")
2025 .contains("phone")
2026 );
2027 assert!(ValidationType::Slug.default_message("url").contains("slug"));
2028 assert!(
2029 ValidationType::Hex
2030 .default_message("color")
2031 .contains("hexadecimal")
2032 );
2033 assert!(
2034 ValidationType::Base64
2035 .default_message("data")
2036 .contains("base64")
2037 );
2038 assert!(
2039 ValidationType::Json
2040 .default_message("config")
2041 .contains("JSON")
2042 );
2043 assert!(
2044 ValidationType::StartsWith("test".to_string())
2045 .default_message("field")
2046 .contains("start with")
2047 );
2048 assert!(
2049 ValidationType::EndsWith(".json".to_string())
2050 .default_message("file")
2051 .contains("end with")
2052 );
2053 assert!(
2054 ValidationType::Contains("keyword".to_string())
2055 .default_message("text")
2056 .contains("contain")
2057 );
2058 assert!(
2059 ValidationType::Length { min: 5, max: 10 }
2060 .default_message("text")
2061 .contains("between")
2062 );
2063
2064 assert!(
2066 ValidationType::Negative
2067 .default_message("balance")
2068 .contains("negative")
2069 );
2070 assert!(
2071 ValidationType::NonNegative
2072 .default_message("count")
2073 .contains("not be negative")
2074 );
2075 assert!(
2076 ValidationType::NonPositive
2077 .default_message("debt")
2078 .contains("not be positive")
2079 );
2080 assert!(
2081 ValidationType::Integer
2082 .default_message("count")
2083 .contains("integer")
2084 );
2085 assert!(
2086 ValidationType::MultipleOf(5.0)
2087 .default_message("value")
2088 .contains("multiple")
2089 );
2090 assert!(
2091 ValidationType::Finite
2092 .default_message("value")
2093 .contains("finite")
2094 );
2095
2096 assert!(
2098 ValidationType::MaxItems(10)
2099 .default_message("items")
2100 .contains("at most")
2101 );
2102 assert!(
2103 ValidationType::Items { min: 1, max: 5 }
2104 .default_message("tags")
2105 .contains("between")
2106 );
2107 assert!(
2108 ValidationType::Unique
2109 .default_message("items")
2110 .contains("unique")
2111 );
2112
2113 assert!(
2115 ValidationType::Future
2116 .default_message("expiry")
2117 .contains("future")
2118 );
2119 assert!(
2120 ValidationType::PastOrPresent
2121 .default_message("login")
2122 .contains("not be in the future")
2123 );
2124 assert!(
2125 ValidationType::FutureOrPresent
2126 .default_message("deadline")
2127 .contains("not be in the past")
2128 );
2129 assert!(
2130 ValidationType::Before("2025-01-01".to_string())
2131 .default_message("date")
2132 .contains("before")
2133 );
2134
2135 assert!(
2137 ValidationType::Required
2138 .default_message("field")
2139 .contains("required")
2140 );
2141 assert!(
2142 ValidationType::NotEmpty
2143 .default_message("list")
2144 .contains("not be empty")
2145 );
2146 assert!(
2147 ValidationType::Custom("strongPassword".to_string())
2148 .default_message("password")
2149 .contains("custom")
2150 );
2151 }
2152
2153 #[test]
2154 fn test_validation_type_validator_names() {
2155 assert_eq!(ValidationType::Url.validator_name(), "url");
2156 assert_eq!(ValidationType::Cuid.validator_name(), "cuid");
2157 assert_eq!(ValidationType::Cuid2.validator_name(), "cuid2");
2158 assert_eq!(ValidationType::NanoId.validator_name(), "nanoid");
2159 assert_eq!(ValidationType::Ulid.validator_name(), "ulid");
2160 assert_eq!(ValidationType::Alpha.validator_name(), "alpha");
2161 assert_eq!(
2162 ValidationType::Alphanumeric.validator_name(),
2163 "alphanumeric"
2164 );
2165 assert_eq!(ValidationType::Lowercase.validator_name(), "lowercase");
2166 assert_eq!(ValidationType::Uppercase.validator_name(), "uppercase");
2167 assert_eq!(ValidationType::Trim.validator_name(), "trim");
2168 assert_eq!(
2169 ValidationType::NoWhitespace.validator_name(),
2170 "no_whitespace"
2171 );
2172 assert_eq!(ValidationType::Ip.validator_name(), "ip");
2173 assert_eq!(ValidationType::Ipv4.validator_name(), "ipv4");
2174 assert_eq!(ValidationType::Ipv6.validator_name(), "ipv6");
2175 assert_eq!(ValidationType::CreditCard.validator_name(), "credit_card");
2176 assert_eq!(ValidationType::Phone.validator_name(), "phone");
2177 assert_eq!(ValidationType::Slug.validator_name(), "slug");
2178 assert_eq!(ValidationType::Hex.validator_name(), "hex");
2179 assert_eq!(ValidationType::Base64.validator_name(), "base64");
2180 assert_eq!(ValidationType::Json.validator_name(), "json");
2181 assert_eq!(
2182 ValidationType::StartsWith("".to_string()).validator_name(),
2183 "starts_with"
2184 );
2185 assert_eq!(
2186 ValidationType::EndsWith("".to_string()).validator_name(),
2187 "ends_with"
2188 );
2189 assert_eq!(
2190 ValidationType::Contains("".to_string()).validator_name(),
2191 "contains"
2192 );
2193 assert_eq!(
2194 ValidationType::Length { min: 0, max: 0 }.validator_name(),
2195 "length"
2196 );
2197 assert_eq!(ValidationType::Max(0.0).validator_name(), "max");
2198 assert_eq!(ValidationType::Negative.validator_name(), "negative");
2199 assert_eq!(ValidationType::NonNegative.validator_name(), "non_negative");
2200 assert_eq!(ValidationType::NonPositive.validator_name(), "non_positive");
2201 assert_eq!(ValidationType::Integer.validator_name(), "integer");
2202 assert_eq!(
2203 ValidationType::MultipleOf(0.0).validator_name(),
2204 "multiple_of"
2205 );
2206 assert_eq!(ValidationType::Finite.validator_name(), "finite");
2207 assert_eq!(ValidationType::MaxItems(0).validator_name(), "max_items");
2208 assert_eq!(
2209 ValidationType::Items { min: 0, max: 0 }.validator_name(),
2210 "items"
2211 );
2212 assert_eq!(ValidationType::Unique.validator_name(), "unique");
2213 assert_eq!(ValidationType::NonEmpty.validator_name(), "non_empty");
2214 assert_eq!(ValidationType::Future.validator_name(), "future");
2215 assert_eq!(
2216 ValidationType::PastOrPresent.validator_name(),
2217 "past_or_present"
2218 );
2219 assert_eq!(
2220 ValidationType::FutureOrPresent.validator_name(),
2221 "future_or_present"
2222 );
2223 assert_eq!(
2224 ValidationType::After("".to_string()).validator_name(),
2225 "after"
2226 );
2227 assert_eq!(
2228 ValidationType::Before("".to_string()).validator_name(),
2229 "before"
2230 );
2231 assert_eq!(ValidationType::Required.validator_name(), "required");
2232 assert_eq!(ValidationType::NotEmpty.validator_name(), "not_empty");
2233 assert_eq!(ValidationType::OneOf(vec![]).validator_name(), "one_of");
2234 assert_eq!(
2235 ValidationType::Custom("".to_string()).validator_name(),
2236 "custom"
2237 );
2238 }
2239
2240 #[test]
2241 fn test_field_validation_has_rules() {
2242 let mut validation = FieldValidation::new();
2243 assert!(!validation.has_numeric_rules());
2244 assert!(!validation.has_array_rules());
2245
2246 validation.add_rule(ValidationRule::new(
2247 ValidationType::Min(0.0),
2248 Span::new(0, 0),
2249 ));
2250 assert!(validation.has_numeric_rules());
2251
2252 let mut arr_validation = FieldValidation::new();
2253 arr_validation.add_rule(ValidationRule::new(
2254 ValidationType::MinItems(1),
2255 Span::new(0, 0),
2256 ));
2257 assert!(arr_validation.has_array_rules());
2258
2259 let mut date_validation = FieldValidation::new();
2261 date_validation.add_rule(ValidationRule::new(ValidationType::Past, Span::new(0, 0)));
2262 assert!(date_validation.rules.iter().any(|r| r.is_date_rule()));
2263 }
2264
2265 #[test]
2266 fn test_parse_validation_rule_more_validators() {
2267 let span = Span::new(0, 0);
2268
2269 let url = parse_validation_rule("url", span).unwrap();
2271 assert!(matches!(url.rule_type, ValidationType::Url));
2272
2273 let cuid = parse_validation_rule("cuid", span).unwrap();
2274 assert!(matches!(cuid.rule_type, ValidationType::Cuid));
2275
2276 let cuid2 = parse_validation_rule("cuid2", span).unwrap();
2277 assert!(matches!(cuid2.rule_type, ValidationType::Cuid2));
2278
2279 let nanoid = parse_validation_rule("nanoid", span).unwrap();
2280 assert!(matches!(nanoid.rule_type, ValidationType::NanoId));
2281
2282 let ulid = parse_validation_rule("ulid", span).unwrap();
2283 assert!(matches!(ulid.rule_type, ValidationType::Ulid));
2284
2285 let alpha = parse_validation_rule("alpha", span).unwrap();
2286 assert!(matches!(alpha.rule_type, ValidationType::Alpha));
2287
2288 let alphanumeric = parse_validation_rule("alphanumeric", span).unwrap();
2289 assert!(matches!(
2290 alphanumeric.rule_type,
2291 ValidationType::Alphanumeric
2292 ));
2293
2294 let lowercase = parse_validation_rule("lowercase", span).unwrap();
2295 assert!(matches!(lowercase.rule_type, ValidationType::Lowercase));
2296
2297 let uppercase = parse_validation_rule("uppercase", span).unwrap();
2298 assert!(matches!(uppercase.rule_type, ValidationType::Uppercase));
2299
2300 let trim = parse_validation_rule("trim", span).unwrap();
2301 assert!(matches!(trim.rule_type, ValidationType::Trim));
2302
2303 let no_whitespace = parse_validation_rule("noWhitespace", span).unwrap();
2304 assert!(matches!(
2305 no_whitespace.rule_type,
2306 ValidationType::NoWhitespace
2307 ));
2308
2309 let ip = parse_validation_rule("ip", span).unwrap();
2310 assert!(matches!(ip.rule_type, ValidationType::Ip));
2311
2312 let ipv4 = parse_validation_rule("ipv4", span).unwrap();
2313 assert!(matches!(ipv4.rule_type, ValidationType::Ipv4));
2314
2315 let ipv6 = parse_validation_rule("ipv6", span).unwrap();
2316 assert!(matches!(ipv6.rule_type, ValidationType::Ipv6));
2317
2318 let credit_card = parse_validation_rule("creditCard", span).unwrap();
2319 assert!(matches!(credit_card.rule_type, ValidationType::CreditCard));
2320
2321 let phone = parse_validation_rule("phone", span).unwrap();
2322 assert!(matches!(phone.rule_type, ValidationType::Phone));
2323
2324 let slug = parse_validation_rule("slug", span).unwrap();
2325 assert!(matches!(slug.rule_type, ValidationType::Slug));
2326
2327 let hex = parse_validation_rule("hex", span).unwrap();
2328 assert!(matches!(hex.rule_type, ValidationType::Hex));
2329
2330 let base64 = parse_validation_rule("base64", span).unwrap();
2331 assert!(matches!(base64.rule_type, ValidationType::Base64));
2332
2333 let json = parse_validation_rule("json", span).unwrap();
2334 assert!(matches!(json.rule_type, ValidationType::Json));
2335
2336 let negative = parse_validation_rule("negative", span).unwrap();
2338 assert!(matches!(negative.rule_type, ValidationType::Negative));
2339
2340 let non_negative = parse_validation_rule("nonNegative", span).unwrap();
2341 assert!(matches!(
2342 non_negative.rule_type,
2343 ValidationType::NonNegative
2344 ));
2345
2346 let non_positive = parse_validation_rule("nonPositive", span).unwrap();
2347 assert!(matches!(
2348 non_positive.rule_type,
2349 ValidationType::NonPositive
2350 ));
2351
2352 let integer = parse_validation_rule("integer", span).unwrap();
2353 assert!(matches!(integer.rule_type, ValidationType::Integer));
2354
2355 let finite = parse_validation_rule("finite", span).unwrap();
2356 assert!(matches!(finite.rule_type, ValidationType::Finite));
2357
2358 let unique = parse_validation_rule("unique", span).unwrap();
2360 assert!(matches!(unique.rule_type, ValidationType::Unique));
2361
2362 let non_empty = parse_validation_rule("nonEmpty", span).unwrap();
2363 assert!(matches!(non_empty.rule_type, ValidationType::NonEmpty));
2364
2365 let past = parse_validation_rule("past", span).unwrap();
2367 assert!(matches!(past.rule_type, ValidationType::Past));
2368
2369 let future = parse_validation_rule("future", span).unwrap();
2370 assert!(matches!(future.rule_type, ValidationType::Future));
2371
2372 let past_or_present = parse_validation_rule("pastOrPresent", span).unwrap();
2373 assert!(matches!(
2374 past_or_present.rule_type,
2375 ValidationType::PastOrPresent
2376 ));
2377
2378 let future_or_present = parse_validation_rule("futureOrPresent", span).unwrap();
2379 assert!(matches!(
2380 future_or_present.rule_type,
2381 ValidationType::FutureOrPresent
2382 ));
2383
2384 let required = parse_validation_rule("required", span).unwrap();
2386 assert!(matches!(required.rule_type, ValidationType::Required));
2387
2388 let not_empty = parse_validation_rule("notEmpty", span).unwrap();
2389 assert!(matches!(not_empty.rule_type, ValidationType::NotEmpty));
2390 }
2391
2392 #[test]
2393 fn test_parse_validation_rule_with_string_args() {
2394 let span = Span::new(0, 0);
2395
2396 let starts_with = parse_validation_rule(r#"startsWith("PREFIX_")"#, span).unwrap();
2397 if let ValidationType::StartsWith(prefix) = starts_with.rule_type {
2398 assert_eq!(prefix, "PREFIX_");
2399 } else {
2400 panic!("Expected StartsWith");
2401 }
2402
2403 let ends_with = parse_validation_rule(r#"endsWith(".json")"#, span).unwrap();
2404 if let ValidationType::EndsWith(suffix) = ends_with.rule_type {
2405 assert_eq!(suffix, ".json");
2406 } else {
2407 panic!("Expected EndsWith");
2408 }
2409
2410 let contains = parse_validation_rule(r#"contains("keyword")"#, span).unwrap();
2411 if let ValidationType::Contains(substring) = contains.rule_type {
2412 assert_eq!(substring, "keyword");
2413 } else {
2414 panic!("Expected Contains");
2415 }
2416
2417 let custom = parse_validation_rule(r#"custom("myValidator")"#, span).unwrap();
2418 if let ValidationType::Custom(name) = custom.rule_type {
2419 assert_eq!(name, "myValidator");
2420 } else {
2421 panic!("Expected Custom");
2422 }
2423
2424 let after = parse_validation_rule(r#"after("2024-01-01")"#, span).unwrap();
2425 if let ValidationType::After(date) = after.rule_type {
2426 assert_eq!(date, "2024-01-01");
2427 } else {
2428 panic!("Expected After");
2429 }
2430
2431 let before = parse_validation_rule(r#"before("2025-12-31")"#, span).unwrap();
2432 if let ValidationType::Before(date) = before.rule_type {
2433 assert_eq!(date, "2025-12-31");
2434 } else {
2435 panic!("Expected Before");
2436 }
2437 }
2438
2439 #[test]
2440 fn test_parse_validation_rule_numeric_args() {
2441 let span = Span::new(0, 0);
2442
2443 let min = parse_validation_rule("min(10)", span).unwrap();
2444 if let ValidationType::Min(n) = min.rule_type {
2445 assert!((n - 10.0).abs() < f64::EPSILON);
2446 } else {
2447 panic!("Expected Min");
2448 }
2449
2450 let max = parse_validation_rule("max(100)", span).unwrap();
2451 if let ValidationType::Max(n) = max.rule_type {
2452 assert!((n - 100.0).abs() < f64::EPSILON);
2453 } else {
2454 panic!("Expected Max");
2455 }
2456
2457 let multiple_of = parse_validation_rule("multipleOf(5)", span).unwrap();
2458 if let ValidationType::MultipleOf(n) = multiple_of.rule_type {
2459 assert!((n - 5.0).abs() < f64::EPSILON);
2460 } else {
2461 panic!("Expected MultipleOf");
2462 }
2463
2464 let min_items = parse_validation_rule("minItems(1)", span).unwrap();
2465 assert!(matches!(min_items.rule_type, ValidationType::MinItems(1)));
2466
2467 let max_items = parse_validation_rule("maxItems(10)", span).unwrap();
2468 assert!(matches!(max_items.rule_type, ValidationType::MaxItems(10)));
2469
2470 let length = parse_validation_rule("length(5, 100)", span).unwrap();
2471 if let ValidationType::Length { min, max } = length.rule_type {
2472 assert_eq!(min, 5);
2473 assert_eq!(max, 100);
2474 } else {
2475 panic!("Expected Length");
2476 }
2477
2478 let items = parse_validation_rule("items(1, 10)", span).unwrap();
2479 if let ValidationType::Items { min, max } = items.rule_type {
2480 assert_eq!(min, 1);
2481 assert_eq!(max, 10);
2482 } else {
2483 panic!("Expected Items");
2484 }
2485 }
2486
2487 #[test]
2488 fn test_parse_validation_rule_unknown() {
2489 let span = Span::new(0, 0);
2490 assert!(parse_validation_rule("unknownValidator", span).is_none());
2491 }
2492
2493 #[test]
2494 fn test_field_metadata_more_tags() {
2495 let span = Span::new(0, 0);
2496 let tags = vec![
2497 DocTag::new("internal", None, span),
2498 DocTag::new(
2499 "description",
2500 Some("A detailed description".to_string()),
2501 span,
2502 ),
2503 DocTag::new("seeAlso", Some("otherField".to_string()), span),
2504 DocTag::new("omitFromInput", None, span),
2505 DocTag::new("omitFromOutput", None, span),
2506 ];
2507
2508 let meta = FieldMetadata::from_tags(&tags);
2509
2510 assert!(meta.internal);
2511 assert_eq!(meta.description, Some("A detailed description".to_string()));
2512 assert_eq!(meta.see_also, vec!["otherField".to_string()]);
2513 assert!(meta.omit_from_input);
2514 assert!(meta.omit_from_output);
2515 }
2516
2517 #[test]
2518 fn test_field_permissions_none() {
2519 let perms = FieldPermissions::none();
2520 assert!(!perms.read);
2521 assert!(!perms.create);
2522 assert!(!perms.update);
2523 assert!(!perms.filter);
2524 assert!(!perms.sort);
2525 }
2526
2527 #[test]
2528 fn test_enhanced_documentation_no_validation() {
2529 let raw = "Just a simple description";
2530 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2531
2532 assert_eq!(doc.text, "Just a simple description");
2533 assert!(!doc.has_validation());
2534 assert_eq!(doc.validation.len(), 0);
2535 assert!(doc.tags.is_empty());
2536 }
2537
2538 #[test]
2539 fn test_enhanced_documentation_readonly() {
2540 let raw = r#"ID field
2541@readonly"#;
2542
2543 let doc = EnhancedDocumentation::parse(raw, Span::new(0, 0));
2544
2545 assert!(doc.is_readonly());
2546 assert!(!doc.is_hidden());
2547 assert!(!doc.is_sensitive());
2548 }
2549}