-
Notifications
You must be signed in to change notification settings - Fork 0
Messages And Metadata
A failure is more than text. Rule.withMessage sets fixed text; Rule.mustWithArguments captures named values while it decides, and the message renders them through {Name} placeholders. Built-in checks already provide their bounds and measured values this way. Localization.localized picks the message from a catalog by the record's own locale:
module Ex06Messages
import Std.Io as Io
import PuduLangValidator.Localization as Localization
import PuduLangValidator.Rule as Rule
import PuduLangValidator.Rules.Number as NumberRule
import PuduLangValidator.Validator as Validator
type Customer = { name: Str, age: Int, locale: Str }
fn catalog() -> Localization.Catalog {
Localization.withMessage(Localization.withMessage(Localization.empty(),
"en", "AgeCheck", "You must be 18 or older."), "es", "AgeCheck", "Debes tener 18 años o más.")
}
fn ageValidator() -> Validator.Validator[Customer] {
let age = Rule.build(Localization.localized(
NumberRule.greaterThanOrEqualTo(Rule.ruleFor("age", |customer: Customer| customer.age), 18),
catalog(), |customer: Customer| customer.locale, "AgeCheck", "You must be 18 or older."))
Validator.add(Validator.create(), age)
}
fn arguedValidator() -> Validator.Validator[Customer] {
let age = Rule.build(Rule.withCode(Rule.withMessage(Rule.mustWithArguments(
Rule.ruleFor("age", |customer: Customer| customer.age),
fn(_customer: Customer, age: Int) -> Option[Array[(Str, Str)]] {
if age >= 18 { None } else { Some([("Minimum", "18")]) }
}), "Too young: need \{Minimum\} years."), "AgeCheck"))
Validator.add(Validator.create(), age)
}
export fn main() -> Int {
let validator = ageValidator()
let spanish = Validator.validate(&validator, Customer{name: "Bo", age: 3, locale: "es"})
let argued = Validator.validate(&arguedValidator(), Customer{name: "Bo", age: 3, locale: "en"})
let line = "es=" + spanish.errors[0].errorMessage + " | argued=" + argued.errors[0].errorMessage
match Io.writeLine(line) {
case Ok(_) => if spanish.errors[0].errorMessage == "Debes tener 18 años o más."
&& argued.errors[0].errorMessage == "Too young: need 18 years." { 0 } else { 2 }
case Err(_) => 1
}
}
Output:
es=Debes tener 18 años o más. | argued=Too young: need 18 years.
Every failure also carries a stable errorCode, a severity, and a customState string for application use. Rule.withSeverity and Rule.withState fix them; Rule.withSeverityFrom and Rule.withStateFrom compute them from the record and the selected value, so one rule can warn for small problems and error for large ones. Three placeholders are always available: {PropertyName}, {PropertyPath}, and {PropertyValue}.
Localization.messageFor reads one entry from a catalog without validating anything: the
record's locale, a failure code, and a fallback for a missing entry. Locales match
case-insensitively, so "ES" finds the "es" entry:
module Ex16Catalog
import Std.Io as Io
import PuduLangValidator.Localization as Localization
fn catalog() -> Localization.Catalog {
Localization.withMessage(Localization.withMessage(Localization.empty(),
"en", "AgeCheck", "You must be 18 or older."), "es", "AgeCheck", "Debes tener 18 años o más.")
}
export fn main() -> Int {
let texts = catalog()
let spanish = Localization.messageFor(&texts, "es", "AgeCheck", "You must be 18 or older.")
let missing = Localization.messageFor(&texts, "fr", "AgeCheck", "You must be 18 or older.")
let line = "es=" + spanish + " | fr=" + missing
match Io.writeLine(line) {
case Ok(_) => if spanish == "Debes tener 18 años o más." && missing == "You must be 18 or older." { 0 } else { 2 }
case Err(_) => 1
}
}
Output:
es=Debes tener 18 años o más. | fr=You must be 18 or older.
Spanish resolves from the catalog; French has no entry, so the caller's fallback answers. The
localized rule above uses the same lookup with the record's own locale on every failure.
Rule.mustWithArguments captures named values while it decides; Rule.withArgument fixes one and
Rule.withArgumentFrom computes one from the record and the value, and the message renders them
through {Name} placeholders. Rule.withName renames a builder for {PropertyName}, and
Rule.overridePropertyName re-roots a rule's failures at another path:
module Ex18Args
import Std.Io as Io
import PuduLangValidator.Result as Validation
import PuduLangValidator.Rule as Rule
import PuduLangValidator.Validator as Validator
type Customer = { name: Str, age: Int }
fn argsValidator() -> Validator.Validator[Customer] {
let age = Rule.build(Rule.withArgument(Rule.withArgumentFrom(
Rule.withMessage(Rule.mustWithArguments(Rule.ruleFor("age", |customer: Customer| customer.age),
fn(_root: Customer, age: Int) -> Option[Array[(Str, Str)]] {
if age >= 18 { None } else { Some([("Minimum", "18")]) }
}), "Too young: need \{Minimum\} \{Unit\}, have \{Actual\}."),
"Actual", fn(_root: Customer, age: Int) -> Str { show(age) }), "Unit", "years"))
let named = Rule.build(Rule.withMessage(
Rule.must(Rule.withName(Rule.ruleFor("name", |customer: Customer| customer.name),
"Display"), |name: Str| !name.isEmpty()), "Bad \{PropertyName\}."))
let aliased = Rule.build(Rule.must(
Rule.overridePropertyName(Rule.ruleFor("name", |customer: Customer| customer.name), "alias"),
|name: Str| !name.isEmpty()))
let flagged = Rule.build(Rule.withState(Rule.withSeverity(Rule.withCode(Rule.withMessage(
Rule.must(Rule.ruleFor("name", |customer: Customer| customer.name), |name: Str| name != "banned"),
"That name is retired."), "Retired"), Validation.Warning), "legacy"))
Validator.add(Validator.add(Validator.add(Validator.add(Validator.create(), age), named), aliased), flagged)
}
export fn main() -> Int {
let result = Validator.validate(&argsValidator(), Customer{name: "", age: 3})
let retired = Validator.validate(&argsValidator(), Customer{name: "banned", age: 30})
let flag = retired.errors[0]
let line = "young=" + result.errors[0].errorMessage
+ " shown=" + result.errors[1].errorMessage
+ " alias=" + result.errors[2].propertyName
+ " retired=" + show(flag.severity) + "/" + flag.customState
match Io.writeLine(line) {
case Ok(_) => if result.errors[0].errorMessage == "Too young: need 18 years, have 3."
&& result.errors[1].errorMessage == "Bad Display."
&& result.errors[2].propertyName == "alias"
&& retired.errors.length() == 1 && flag.severity == Validation.Warning
&& flag.customState == "legacy" { 0 } else { 2 }
case Err(_) => 1
}
}
Output:
young=Too young: need 18 years, have 3. shown=Bad Display. alias=alias retired=Warning/legacy
The age check captures Minimum itself while Actual is computed and Unit is fixed, so one
message carries all three. The renamed rule renders its display name, and the re-rooted rule
reports alias instead of name. The static withSeverity and withState fix the retired
name's classification outright: Warning severity with "legacy" state, no computation.