
Binding.pudu
Pudu148 lines6.1 KB
1/** @Environment.Binding.Module — records read from variables by derivation */2module PuduLangEnvironment.Binding34import Std.App.Secret as Secret5import Std.Meta as Meta6import PuduLangEnvironment.Constants.Names as Names7import PuduLangEnvironment.Domain.Keys as Keys8import PuduLangEnvironment.Domain.Values as Values9import PuduLangEnvironment as Environment10import PuduLangEnvironment.Variables as Variables1112/** @Environment.Binding.Variable — a type one variable's text spells */13export trait Variable {14 15 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem]16 17 fn optional() -> Bool18 19 fn rendered(self: &Self) -> Str20}2122impl Variable for Str {23 24 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, |held: Str| Some(held), "") }25 26 fn optional() -> Bool { false }27 28 fn rendered(self: &Self) -> Str { show(*self) }29}3031impl Variable for Int {32 33 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, Values.toInt, Names.KIND_INT) }34 35 fn optional() -> Bool { false }36 37 fn rendered(self: &Self) -> Str { show(*self) }38}3940impl Variable for Float64 {41 42 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, Values.toFloat, Names.KIND_FLOAT) }43 44 fn optional() -> Bool { false }45 46 fn rendered(self: &Self) -> Str { show(*self) }47}4849impl Variable for Decimal {50 51 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, Values.toDecimal, Names.KIND_DECIMAL) }52 53 fn optional() -> Bool { false }54 55 fn rendered(self: &Self) -> Str { show(*self) }56}5758impl Variable for Bool {59 60 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, Values.toBool, Names.KIND_BOOL) }61 62 fn optional() -> Bool { false }63 64 fn rendered(self: &Self) -> Str { show(*self) }65}6667impl Variable for Secret.Secret {68 69 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] { converted(key, text, |held: Str| Some(Secret.secret(held)), "") }70 71 fn optional() -> Bool { false }72 73 fn rendered(self: &Self) -> Str { Names.REDACTED }74}7576impl [A: Variable] Variable for Option[A] {77 78 fn fromVariable(key: Str, text: Option[Str]) -> Result[Self, Environment.Problem] {79 match text {80 case Some(_) => Ok(Some(A.fromVariable(key, text) ?))81 case None => Ok(None)82 }83 }84 85 fn optional() -> Bool { true }86 87 fn rendered(self: &Self) -> Str {88 match self {89 case Some(held) => "Some(" + held.rendered() + ")"90 case None => "None"91 }92 }93}9495/** @Environment.Binding.Bind — a record read from environment variables */96export trait Bind {97 98 fn bind(variables: &Variables.Variables) -> Result[Self, Environment.Problem]99}100101102103104105export derive Bind for T: Record {106 fn bind(variables: &Variables.Variables) -> Result[T, Environment.Problem] {107 let missing = Meta.collect[T](fn(field: Meta.Field[T, F]) -> Option[Str] where F: Variable {108 let key = field.attributeOr("env", Keys.fromField(field.name))109 if F.optional() || field.has("default") || Variables.has(variables, key) { None } else { Some(key) }110 })111 if !missing.isEmpty() { return Err(Environment.Missing(missing)) }112 Meta.build[T](fn(field: Meta.Field[T, F]) -> Result[F, Environment.Problem] where F: Variable {113 let key = field.attributeOr("env", Keys.fromField(field.name))114 let found = Variables.find(variables, key)115 let text = if found == None && field.has("default") { Some(field.attributeOr("default", "")) } else { found }116 F.fromVariable(key, text)117 })118 }119}120121/** @Environment.Binding.Redacted — a record rendered with its secrets hidden */122export trait Redacted {123 124 fn redacted(self: &Self) -> Str125}126127128129export derive Redacted for T: Record {130 fn redacted(self: &T) -> Str {131 let parts = Meta.collect[T](fn(field: Meta.Field[T, F]) -> Option[Str] where F: Variable {132 Some(field.name + ": " + if field.has("secret") { Names.REDACTED } else { field.get(self).rendered() })133 })134 Meta.nameOf[T]() + "\{" + parts.join(", ") + "\}"135 }136}137138139fn converted[T](key: Str, text: Option[Str], convert: fn(Str) -> Option[T], kind: Str) -> Result[T, Environment.Problem] {140 match text {141 case Some(held) => match convert(held) {142 case Some(value) => Ok(value)143 case None => Err(Environment.Malformed(key, kind))144 }145 case None => Err(Environment.Missing([key]))146 }147}148