Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-validator

Typed fluent validation rules for Pudu

0.1.1Apache-2.01

InstallClose

AsyncChildTest.pudu

Pudu94 lines5.3 KB

GitHub ↗
1/** @Test.Validation.AsyncChild.Suite — checks nested awaited validation */2module PuduLangValidator.AsyncChildTest34import Std.Json as Json5import Std.Map as Map6import Std.Test as Test7import PuduLangValidator.Async as Async8import PuduLangValidator.Result as Validation9import PuduLangValidator.Rule as Rule10import PuduLangValidator.Validator as Validator1112type Child = { code: Str, city: Str }13type Root = { child: Child, optional: Option[Child], items: Array[Child], enabled: Bool }1415async fn validCode(_root: Child, code: Str, data: Rule.ContextData) -> Bool {16  match Map.get(&data, "expected") {17    case Some(Json.Text(expected)) => code == expected18    case _ => false19  }20}2122fn childRules() -> Async.AsyncValidator[Child] {23  let code = Async.mustAsyncWithContext("code", |child: Child| child.code,24    validCode, "Code differs from context.", "Code")25  let city = Async.inRuleSet(Async.mustAsync("city", |child: Child| child.city,26      async fn(_root: Child, value: Str) -> Bool { !value.isEmpty() },27      "City is required.", "City"), "shipping")28  Async.add(Async.add(Async.create(), code), city)29}3031fn rootRules() -> Async.AsyncValidator[Root] {32  let child = childRules()33  let direct = Async.setValidatorAsync("child", |root: Root| root.child, child)34  let optional = Async.setOptionalValidatorAsync("optional", |root: Root| root.optional, childRules())35  let items = Async.forEachValidatorAsync("items", |root: Root| root.items, childRules())36  Async.add(Async.add(Async.add(Async.create(), direct), optional), items)37}3839async fn main() -> Int {40  let validator = rootRules()41  let data: Rule.ContextData = mapOf([("expected", Json.Text("ok"))])42  let root = Root {43    child: Child{code: "bad", city: ""}, optional: None,44    items: [Child{code: "bad", city: ""}, Child{code: "bad", city: ""}], enabled: false45  }46  let defaultResult = Async.validateAsyncWithData(&validator, root, Validator.defaults(), data).await47  let selected = Async.validateAsyncWithData(&validator, root,48    Validator.includeProperties(Validator.defaults(), ["items[1].code"]), data).await49  let allCodes = Async.validateAsyncWithData(&validator, root,50    Validator.includeProperties(Validator.defaults(), ["items[].code"]), data).await51  let dotted = Async.add(Async.create(),52    Async.forEachValidatorAsync("group.items", |value: Root| value.items, childRules()))53  let parentSelected = Async.validateAsyncWithData(&dotted, root,54    Validator.includeProperties(Validator.defaults(), ["group"]), data).await55  let shipping = Async.validateAsyncWithData(&validator, root,56    Validator.onlyRuleSets(["shipping"]), data).await57  let present = Root{..root, optional: Some(Child{code: "bad", city: ""})}58  let optionalResult = Async.validateAsyncWithData(&validator, present,59    Validator.includeProperties(Validator.defaults(), ["optional.code"]), data).await60  let custom = Async.customAsync("root", async fn(_root: Root, _data: Rule.ContextData) -> Array[Validation.Failure] {61      [Validation.Failure{propertyName: "root", errorMessage: "Root failed.", errorCode: "Root",62          severity: Validation.Error, customState: "" }]63    })64  let conditional = Async.whenAsync(custom, async fn(value: Root, _data: Rule.ContextData) -> Bool { value.enabled })65  let conditionalValidator = Async.add(Async.create(), conditional)66  let skipped = Async.validateAsyncWithData(&conditionalValidator, root, Validator.defaults(), data).await67  let allowed = Async.validateAsyncWithData(&conditionalValidator, Root{..root, enabled: true},68    Validator.defaults(), data).await69  let namedChild = Async.add(Async.create(), Async.inRuleSet(Async.mustAsync("city", |child: Child| child.city,70        async fn(_root: Child, value: Str) -> Bool { !value.isEmpty() },71        "City is required.", "City"), "shipping"))72  let namedParent = Async.add(Async.create(),73    Async.setValidatorAsync("child", |root: Root| root.child, namedChild))74  let namedDefault = Async.validateAsync(&namedParent,75    Root{child: Child{code: "ok", city: ""}, optional: None, items: [], enabled: false}).await76  let checks = Test.suite("AsyncChild", &[77      Test.equals("child failures keep source order", &defaultResult.errors.map(|failure: Validation.Failure| failure.propertyName),78          &["child.code", "items[0].code", "items[1].code"]),79      Test.equals("one indexed property reaches one child", &selected.errors.map(|failure: Validation.Failure| failure.propertyName),80          &["items[1].code"]),81      Test.equals("empty index selects each async child field", &allCodes.errors.map(|failure: Validation.Failure| failure.propertyName),82          &["items[0].code", "items[1].code"]),83      Test.equals("parent selection reaches dotted collection", &parentSelected.errors.map(|failure: Validation.Failure| failure.propertyName),84          &["group.items[0].code", "group.items[1].code"]),85      Test.equals("named set reaches nested children", &shipping.errors.map(|failure: Validation.Failure| failure.propertyName),86          &["child.city", "items[0].city", "items[1].city"]),87      Test.equals("present optional child is awaited", &optionalResult.errors[0].propertyName, &"optional.code"),88      Test.equals("false awaited condition skips custom check", &skipped.errors.length(), &0),89      Test.equals("true awaited condition runs custom check", &allowed.errors[0].errorCode, &"Root"),90      Test.equals("named-only child stays silent by default", &namedDefault.errors.length(), &0)91    ])92  Test.report(&Test.run(&checks))93}94