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