Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-validator

Typed fluent validation rules for Pudu

0.1.1Apache-2.01

InstallClose

Decimal.pudu

Pudu42 lines2.0 KB

GitHub ↗
1/** @Validation.Rules.Decimal.Module — checks exact decimal shape */2module PuduLangValidator.Rules.Decimal34import Std.Text as Text5import PuduLangValidator.Rule as Rule67/** @Validation.Rules.Decimal.Shape — counts displayed decimal digits */8type Shape = { precision: Int, scale: Int, wholeDigits: Int }910/// Reserve `precision - scale` whole digits and allow at most `scale` fractional digits.11export fn precisionScale[T](12  builder: Rule.RuleBuilder[T, Decimal], precision: Int, scale: Int, ignoreTrailingZeros: Bool13) -> Rule.RuleBuilder[T, Decimal] {14  let checked = Rule.withCode(Rule.withMessage(Rule.mustWithArguments(builder,15        fn(_root: T, value: Decimal) -> Option[Array[(Str, Str)]] {16          let actual = shape(value, ignoreTrailingZeros)17          if precision >= 1 && scale >= 0 && scale <= precision &&18          actual.precision <= precision && actual.scale <= scale &&19          actual.wholeDigits <= precision - scale {20            None21          } else {22            Some([("Digits", show(actual.precision)), ("ActualScale", show(actual.scale))])23          }24        }), "Decimal must fit \{ExpectedPrecision\} digits with at most \{ExpectedScale\} after the point."), "PrecisionScale")25  Rule.withArgument(Rule.withArgument(checked, "ExpectedPrecision", show(precision)),26    "ExpectedScale", show(scale))27}2829/// Count significant integer digits and retained fractional digits.30fn shape(value: Decimal, ignoreTrailingZeros: Bool) -> Shape {31  let rendered = value.toText()32  let unsigned = if rendered.startsWith("-") { Text.drop(rendered, 1) } else { rendered }33  let parts = unsigned.split(".")34  let whole = Text.dropWhile(parts[0], |character: Char| character == '0')35  let fractional = if parts.length() > 1 { parts[1] } else { "" }36  let kept = if ignoreTrailingZeros {37    Text.dropWhileEnd(fractional, |character: Char| character == '0')38  } else { fractional }39  let digits = whole.length() + kept.length()40  Shape{precision: if digits == 0 { 1 } else { digits }, scale: kept.length(), wholeDigits: whole.length()}41}42