131 lines
4.0 KiB
V
131 lines
4.0 KiB
V
module conf
|
|
|
|
import os
|
|
import toml
|
|
import datatypes { Set }
|
|
|
|
[params]
|
|
pub struct LoadConfig {
|
|
prefix string
|
|
file_suffix string = '_FILE'
|
|
default_path string
|
|
// Allows overwriting
|
|
env map[string]string = os.environ()
|
|
}
|
|
|
|
// 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. It returns two values, with the first indicating
|
|
// whether the env vars were actually present.
|
|
fn (ld LoadConfig) get_env_var(field_name string) !(bool, string) {
|
|
env_var_name := '$ld.prefix$field_name.to_upper()'
|
|
env_file_name := '$ld.prefix$field_name.to_upper()$ld.file_suffix'
|
|
|
|
if env_var_name !in ld.env && env_file_name !in ld.env {
|
|
return false, ''
|
|
}
|
|
|
|
// If they're both set, we report a conflict
|
|
if env_var_name in ld.env && env_file_name in ld.env {
|
|
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_name in ld.env {
|
|
return true, ld.env[env_var_name]
|
|
}
|
|
|
|
// Otherwise, we process the file
|
|
return true, os.read_file(ld.env[env_file_name]) or {
|
|
error('Failed to read file defined in $env_file_name: ${err.msg()}.')
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
pub fn load<T>(ld LoadConfig) !T {
|
|
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 := Set<string>{}
|
|
|
|
// Later, this could be read from an env var as well.
|
|
path := ld.default_path
|
|
|
|
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
|
|
doc := toml.parse_file(path)!
|
|
|
|
$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.')
|
|
}
|
|
|
|
has_value.add(field.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
$for field in T.fields {
|
|
env_present, env_value := ld.get_env_var(field.name)!
|
|
|
|
// The value of an env var will always take precedence over the toml
|
|
// file.
|
|
if env_present {
|
|
$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.')
|
|
}
|
|
|
|
has_value.add(field.name)
|
|
}
|
|
|
|
// Finally, if there's no env var present either, we check whether the
|
|
// variable has a default value. Variables defined with an "empty
|
|
// default" will always be marked as containing a value.
|
|
if 'empty_default' in field.attrs {
|
|
has_value.add(field.name)
|
|
} else if !has_value.exists(field.name) {
|
|
mut has_default := false
|
|
|
|
$if field.typ is string {
|
|
has_default = res.$(field.name) != ''
|
|
} $else $if field.typ is int {
|
|
has_default = res.$(field.name) != 0
|
|
}
|
|
|
|
if has_default {
|
|
has_value.add(field.name)
|
|
}
|
|
}
|
|
|
|
// If there's no value provided in any way, we notify the user with an
|
|
// error.
|
|
if !has_value.exists(field.name) {
|
|
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
|
|
}
|