
AsyncChildTest.pudu
Pudu94 lines5.3 KB
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