
Text.pudu
Pudu100 lines4.7 KB
1/** @Validation.Rules.Text.Module — checks textual property constraints */2module PuduLangValidator.Rules.Text34import Std.Char as Char5import Std.List as List6import Std.Regex as Regex7import Std.Text as Text8import PuduLangValidator.Rule as Rule91011export fn notEmpty[T](builder: Rule.RuleBuilder[T, Str]) -> Rule.RuleBuilder[T, Str] {12 Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str| !value.trim().isEmpty()), "Text is required."), "NotEmpty")13}141516export fn empty[T](builder: Rule.RuleBuilder[T, Str]) -> Rule.RuleBuilder[T, Str] {17 Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str| value.trim().isEmpty()), "Text must be empty."), "Empty")18}192021export fn length[T](builder: Rule.RuleBuilder[T, Str], minimum: Int, maximum: Int) -> Rule.RuleBuilder[T, Str] {22 let checked = Rule.withCode(Rule.withMessage(23 Rule.must(builder, |value: Str| value.length() >= minimum && value.length() <= maximum),24 "Text must contain between \{MinLength\} and \{MaxLength\} characters."25 ), "Length")26 let bounded = Rule.withArgument(Rule.withArgument(checked, "MinLength", show(minimum)), "MaxLength", show(maximum))27 Rule.withArgumentFrom(bounded, "TotalLength", fn(_root: T, value: Str) -> Str { show(value.length()) })28}293031export fn minimumLength[T](builder: Rule.RuleBuilder[T, Str], minimum: Int) -> Rule.RuleBuilder[T, Str] {32 let checked = Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str| value.length() >= minimum),33 "Text must contain at least \{MinLength\} characters."), "MinimumLength")34 Rule.withArgumentFrom(Rule.withArgument(checked, "MinLength", show(minimum)),35 "TotalLength", fn(_root: T, value: Str) -> Str { show(value.length()) })36}373839export fn maximumLength[T](builder: Rule.RuleBuilder[T, Str], maximum: Int) -> Rule.RuleBuilder[T, Str] {40 let checked = Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str| value.length() <= maximum),41 "Text must contain no more than \{MaxLength\} characters."), "MaximumLength")42 Rule.withArgumentFrom(Rule.withArgument(checked, "MaxLength", show(maximum)),43 "TotalLength", fn(_root: T, value: Str) -> Str { show(value.length()) })44}454647export fn emailAddress[T](builder: Rule.RuleBuilder[T, Str]) -> Rule.RuleBuilder[T, Str] {48 Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str|49 Text.countOccurrences(value, "@") == 1 && !value.startsWith("@") && !value.endsWith("@")50 ), "Text must have an internal @ sign."), "EmailAddress")51}52535455export fn matches[T](builder: Rule.RuleBuilder[T, Str], pattern: Str) -> Result[Rule.RuleBuilder[T, Str], Regex.RegexError] {56 let compiled = Regex.compile(pattern) ?57 Ok(Rule.withArgument(Rule.withCode(Rule.withMessage(58 Rule.must(builder, |value: Str| Regex.isMatch(&compiled, value)),59 "Text does not match the required pattern."60 ), "Matches"), "RegularExpression", pattern))61}626364export fn creditCard[T](builder: Rule.RuleBuilder[T, Str]) -> Rule.RuleBuilder[T, Str] {65 Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str| isCardNumber(value)),66 "Card number failed its checksum."), "CreditCard")67}686970export fn enumName[T](71 builder: Rule.RuleBuilder[T, Str], names: Array[Str], caseSensitive: Bool72) -> Rule.RuleBuilder[T, Str] {73 Rule.withCode(Rule.withMessage(Rule.must(builder, |value: Str|74 List.any(&names, |name: Str| if caseSensitive { value == name } else { value.toLower() == name.toLower() })75 ), "Text is not a recognized name."), "EnumName")76}777879fn isCardNumber(value: Str) -> Bool {80 var digits: Array[Int] = []81 for character in value.chars() {82 if character == ' ' || character == '-' { continue }83 if character < '0' || character > '9' { return false }84 let digit = match Char.toDigit(character) {85 case Some(found) => found86 case None => 087 }88 digits = digits.push(digit)89 }90 if digits.length() < 12 || digits.length() > 19 { return false }91 var sum = 092 var index = 093 for digit in digits.reverse() {94 let doubled = if index % 2 == 1 { digit * 2 } else { digit }95 sum = sum + if doubled >= 10 { doubled - 9 } else { doubled }96 index = index + 197 }98 sum % 10 == 099}100