Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-validator

Typed fluent validation rules for Pudu

0.1.1Apache-2.01

InstallClose

Child.pudu

Pudu96 lines4.0 KB

GitHub ↗
1/** @Validation.Rules.Child.Module — composes nested validator failures */2module PuduLangValidator.Rules.Child34import Std.List as List5import PuduLangValidator.Result as Validation6import PuduLangValidator.Rule as Rule7import PuduLangValidator.Selection as Selection8import PuduLangValidator.Validator as Validator910/// Run a child validator and prefix its failure paths.11export fn setValidator[T, V](name: Str, select: fn(T) -> V, child: Validator.Validator[V]) -> Rule.Rule[T] {12  let groups = childGroups(&child)13  Rule.Rule{name: name, ruleSets: groups, run: fn(root: T, sets: Array[Str], includeDefault: Bool, properties: Array[Str], data: Rule.ContextData) -> Array[Validation.Failure] {14      let options = Validator.Options{properties: requested(name, &properties), ruleSets: sets, includeDefault: includeDefault}15      prefix(name, &Validator.validateWithData(&child, select(root), options, data).errors)16    } }17}1819/// Skip an absent child; presence can be checked by its own rule.20export fn setOptionalValidator[T, V](21  name: Str, select: fn(T) -> Option[V], child: Validator.Validator[V]22) -> Rule.Rule[T] {23  let groups = childGroups(&child)24  Rule.Rule{name: name, ruleSets: groups, run: fn(root: T, sets: Array[Str], includeDefault: Bool, properties: Array[Str], data: Rule.ContextData) -> Array[Validation.Failure] {25      match select(root) {26        case Some(value) => {27          let options = Validator.Options{properties: requested(name, &properties), ruleSets: sets, includeDefault: includeDefault}28          prefix(name, &Validator.validateWithData(&child, value, options, data).errors)29        }30        case None => []31      }32    } }33}3435/// Run one child validator for each array element in source order.36export fn forEachValidator[T, V](37  name: Str, select: fn(T) -> Array[V], child: Validator.Validator[V]38) -> Rule.Rule[T] {39  let groups = childGroups(&child)40  Rule.Rule{name: name, ruleSets: groups, run: fn(root: T, sets: Array[Str], includeDefault: Bool, properties: Array[Str], data: Rule.ContextData) -> Array[Validation.Failure] {41      var failures: Array[Validation.Failure] = []42      var index = 043      for value in select(root) {44        let path = name + "[" + show(index) + "]"45        if reaches(name, path, &properties) {46          let options = Validator.Options{properties: requested(path, &properties), ruleSets: sets, includeDefault: includeDefault}47          for failure in prefix(path, &Validator.validateWithData(&child, value, options, data).errors) {48            failures = failures.push(failure)49          }50        }51        index = index + 152      }53      failures54    } }55}5657/// Names that let the parent reach the child's selected rule sets.58fn childGroups[V](child: &Validator.Validator[V]) -> Array[Str] {59  var groups: Array[Str] = []60  for rule in child.rules {61    for name in rule.ruleSets {62      if !groups.contains(name) { groups = groups.push(name) }63    }64  }65  if List.any(&child.rules, |rule: Rule.Rule[V]| rule.ruleSets.isEmpty()) { groups = groups.push("default") }66  groups67}6869/// Property names relative to the child, or empty for the whole child.70fn requested(name: Str, properties: &Array[Str]) -> Array[Str] {71  if name.isEmpty() { return *properties }72  var selected: Array[Str] = []73  for wanted in *properties {74    if Selection.covers(wanted, name) { return [] }75    if let Some(relative) = Selection.relative(wanted, name) { selected = selected.push(relative) }76  }77  selected78}7980/// Whether a selected root path reaches this array element.81fn reaches(name: Str, path: Str, properties: &Array[Str]) -> Bool {82  if properties.isEmpty() { return true }83  for wanted in *properties {84    if Selection.overlaps(wanted, path) { return true }85  }86  false87}8889/// Add the root path without changing the child validator's messages.90fn prefix(name: Str, failures: &Array[Validation.Failure]) -> Array[Validation.Failure] {91  failures.map(|failure: Validation.Failure| Validation.Failure {92      ..failure,93      propertyName: if name.isEmpty() { failure.propertyName } else if failure.propertyName.isEmpty() { name } else { name + "." + failure.propertyName }94    })95}96