https://github.com/FlashSystems/just-config#no-data-types makes an interesting argument that configuration shouldn’t be typed. I disagree with the conclusion but agree with the premise. Me likey types, and I think you can get the best of both worlds.
To explain further: The example used is cache_timeout=X
. Should the value of X be constrained? If you’re validating a configuration file, you would constrain X
to be a certain type. If you’re parsing a configuration file, you would not necessarily constrain X
.
So, Parse, don’t validate strikes again.
Going back to the example, we have a situation here of mismatched concerns.
- APIs should be Forwards and Backwards Compatible.
- Configuration is also an API
- Typing by validation is inherently not forwards/backwards compatible.
In the example, X
is originally of type Number
, then Number | Disabled
, then Number | Disabled
(but with support for parsing Infinity
as a number).
So, the proper type of a configuration value is really {key: string, value: bytestring}
. This can be later parsed into a refined type when needed rather than validated at consumption.
Importantly… Parsing here does not prevent you from keeping the unparsed configuration around.
let cfg = getCfg() // Record<string, bytestring>
let cache_timeout = parseCacheTimeout(cfg)
fn parseCacheTimeout(cfg) {
let timeout =
// version 1
try parseNum(cfg.?cache_timeout)
`or`
// version 2
try parseString(cfg?.cache_timeout, "disabled")
`or`
// version 3
try parseInfinity(cfg?.cache_timeout)
// ... version N
// forwards compat
`or`
configDefaultWithWarning(cfg, "cache_timeout")
return timeout
}