conf/conf.v

109 lines
3.2 KiB
Coq
Raw Normal View History

2022-06-22 09:12:59 +02:00
module conf
2022-06-15 12:40:35 +02:00
import os
import toml
// get_env_var tries to read the contents of the given environment variable. It
// looks for either `${env.prefix}${field_name.to_upper()}` or
// `${env.prefix}${field_name.to_upper()}${env.file_suffix}`, returning the
// contents of the file instead if the latter. If both or neither exist, the
// function returns an error.
2022-11-01 20:57:04 +01:00
fn get_env_var(prefix string, field_name string, file_suffix string) !string {
2022-06-15 12:40:35 +02:00
env_var_name := '$prefix$field_name.to_upper()'
env_file_name := '$prefix$field_name.to_upper()$file_suffix'
env_var := os.getenv(env_var_name)
env_file := os.getenv(env_file_name)
// If both are missing, we return an empty string
if env_var == '' && env_file == '' {
return ''
}
// If they're both set, we report a conflict
if env_var != '' && env_file != '' {
return error('Only one of $env_var_name or $env_file_name can be defined.')
}
// If it's the env var itself, we return it.
// I'm pretty sure this also prevents variable ending in _FILE (e.g.
// VIETER_LOG_FILE) from being mistakingely read as an _FILE suffixed env
// var.
if env_var != '' {
return env_var
}
// Otherwise, we process the file
return os.read_file(env_file) or {
error('Failed to read file defined in $env_file_name: ${err.msg()}.')
}
}
[params]
2022-06-15 13:09:57 +02:00
pub struct LoadConfig {
prefix string
file_suffix string = '_FILE'
default_path string
2022-06-15 12:40:35 +02:00
}
// load<T> attempts to create an object of type T from the given path to a toml
// file & environment variables. For each field, it will select either a value
// given from an environment variable, a value defined in the config file or a
// configured default if present, in that order.
2022-11-01 20:57:04 +01:00
pub fn load<T>(conf LoadConfig) !T {
2022-06-15 12:40:35 +02:00
mut res := T{}
// This array allows us to determine later whether the variable is actually
// zero or just a null'ed struct field
mut has_value := map[string]bool{}
// Later, this could be read from an env var as well.
path := conf.default_path
2022-06-15 12:40:35 +02:00
if os.exists(path) {
// We don't use reflect here because reflect also sets any fields not
// in the toml back to their zero value, which we don't want
2022-11-01 20:57:04 +01:00
doc := toml.parse_file(path)!
2022-06-15 12:40:35 +02:00
$for field in T.fields {
s := doc.value(field.name)
if s !is toml.Null {
$if field.typ is string {
res.$(field.name) = s.string()
} $else $if field.typ is int {
res.$(field.name) = s.int()
// This seems to not work in V 0.3.2
//} $else {
// $compile_error('Unsupported config struct field type detected.')
2022-06-15 12:40:35 +02:00
}
has_value[field.name] = true
2022-06-15 12:40:35 +02:00
}
}
}
$for field in T.fields {
2022-11-01 20:57:04 +01:00
env_value := get_env_var(conf.prefix, field.name, conf.file_suffix)!
2022-06-15 12:40:35 +02:00
// The value of an env var will always take precedence over the toml
// file.
if env_value != '' {
$if field.typ is string {
res.$(field.name) = env_value
} $else $if field.typ is int {
res.$(field.name) = env_value.int()
// This seems to not work in V 0.3.2
//} $else {
// $compile_error('Unsupported config struct field type detected.')
2022-06-15 12:40:35 +02:00
}
has_value[field.name] = true
2022-06-15 12:40:35 +02:00
}
2022-12-28 16:44:02 +01:00
if !(has_value[field.name] or { false }) {
2022-06-15 12:40:35 +02:00
return error("Missing config variable '$field.name' with no provided default. Either add it to the config file or provide it using an environment variable.")
}
}
return res
}