
Validator.pudu
Pudu112 lines4.1 KB
1/** @Validation.Validator.Aggregate — composes ordered validation rules */2module PuduLangValidator.Validator34import Std.List as List5import PuduLangValidator.Result as Validation6import PuduLangValidator.Rule as Rule7import PuduLangValidator.Selection as Selection89/** @Validation.Validator.Options — selects named rule subsets */10export type Options = { properties: Array[Str], ruleSets: Array[Str], includeDefault: Bool }1112/** @Validation.Validator.Validator — holds rules for one source type */13export type Validator[T] = {14 rules: Array[Rule.Rule[T]],15 stop: Bool,16 preflight: fn(T) -> Option[Array[Validation.Failure]]17}181920export fn create[T]() -> Validator[T] {21 Validator{rules: [], stop: false, preflight: fn(_root: T) -> Option[Array[Validation.Failure]] { None }}22}232425export fn withPreValidation[T](26 validator: Validator[T], preflight: fn(T) -> Option[Array[Validation.Failure]]27) -> Validator[T] {28 Validator{..validator, preflight: preflight}29}303132export fn add[T](validator: Validator[T], rule: Rule.Rule[T]) -> Validator[T] {33 Validator{..validator, rules: validator.rules.push(rule)}34}353637export fn include[T](validator: Validator[T], other: &Validator[T]) -> Validator[T] {38 var rules = validator.rules39 for rule in other.rules { rules = rules.push(rule) }40 Validator{..validator, rules: rules}41}424344export fn stopOnFirst[T](validator: Validator[T]) -> Validator[T] {45 Validator{..validator, stop: true}46}474849export fn defaults() -> Options { Options{properties: [], ruleSets: [], includeDefault: true} }505152export fn onlyRuleSets(names: Array[Str]) -> Options {53 Options{properties: [], ruleSets: names, includeDefault: false}54}555657export fn allRuleSets() -> Options {58 Options{properties: [], ruleSets: ["*"], includeDefault: true}59}606162export fn includeProperties(options: Options, names: Array[Str]) -> Options {63 Options{..options, properties: names}64}656667export fn includeRulesNotInRuleSet(options: Options) -> Options {68 Options{..options, includeDefault: true}69}707172export fn validate[T](validator: &Validator[T], root: T) -> Validation.ValidationResult {73 validateWith(validator, root, defaults())74}757677export fn validateWith[T](validator: &Validator[T], root: T, options: Options) -> Validation.ValidationResult {78 validateWithData(validator, root, options, mapOf([]))79}808182export fn validateWithData[T](83 validator: &Validator[T], root: T, options: Options, data: Rule.ContextData84) -> Validation.ValidationResult {85 if let Some(early) = validator.preflight(root) { return Validation.fromFailures(early) }86 var failures: Array[Validation.Failure] = []87 for rule in validator.rules {88 if accepts(rule.name, &rule.ruleSets, &options) {89 let found = rule.run(root, options.ruleSets, options.includeDefault, options.properties, data)90 for failure in found { failures = failures.push(failure) }91 if validator.stop && !found.isEmpty() { break }92 }93 }94 Validation.fromFailures(failures)95}969798export fn accepts(path: Str, ruleSets: &Array[Str], options: &Options) -> Bool {99 let pathAllowed = if options.properties.isEmpty() {100 true101 } else {102 List.any(&options.properties, |name: Str| Selection.overlaps(name, path))103 }104 let setAllowed = if ruleSets.isEmpty() {105 options.includeDefault106 } else {107 options.ruleSets.contains("*") || List.any(ruleSets, |name: Str|108 options.ruleSets.contains(name) || (name == "default" && options.includeDefault))109 }110 pathAllowed && setAllowed111}112