2022-02-21 20:40:13 +01:00
module env
import os
2022-04-06 17:51:06 +02:00
import toml
2022-02-21 20:40:13 +01:00
// The prefix that every environment variable should have
const prefix = ' VIETER_'
// The suffix an environment variable in order for it to be loaded from a file
// instead
const file_suffix = ' _ FILE'
fn get_env_var ( field_name string ) ? string {
2022-02-21 20:51:41 +01:00
env_var_name := ' $ env . prefix $ field_name . to_upper () '
env_file_name := ' $ env . prefix $ field_name . to_upper () $ env . file_suffix'
2022-02-21 20:40:13 +01:00
env_var := os . getenv ( env_var_name )
env_file := os . getenv ( env_file_name )
2022-04-06 17:51:06 +02:00
// If both are missing , we return an empty string
2022-02-21 20:40:13 +01:00
if env_var = = ' ' && env_file = = ' ' {
2022-04-06 17:51:06 +02:00
return ' '
2022-02-21 20:40:13 +01:00
}
// 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 . ' )
}
2022-02-21 22:22:36 +01:00
// 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 .
2022-02-21 20:40:13 +01:00
if env_var != ' ' {
return env_var
}
// Otherwise , we process the file
return os . read_file ( env_file ) or {
2022-04-13 22:20:05 +02:00
error ( ' Failed to read file defined in $ env_file_name : $ { err . msg () } . ' )
2022-02-21 20:40:13 +01:00
}
}
2022-04-06 18:02:57 +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-04-06 17:51:06 +02:00
pub fn load < T > ( path string ) ? T {
2022-04-06 16:52:31 +02:00
mut res := T { }
2022-04-06 17:51:06 +02:00
if os . exists ( path ) {
2022-04-06 18:17:33 +02:00
// We don't use reflect here because reflect also sets any fields not
2022-04-06 19:51:54 +02:00
// in the toml back to their zero value , which we don't want
2022-04-06 18:17:33 +02:00
doc := toml . parse_file ( path ) ?
$ for field in T . fields {
s := doc . value ( field . name )
2022-04-13 14:51:01 +02:00
if s ! is toml . Null {
$ if field . typ is string {
res . $ ( field . name ) = s . string ()
2022-04-13 16:12:22 +02:00
} $ else $ if field . typ is int {
2022-04-13 14:51:01 +02:00
res . $ ( field . name ) = s . int ()
}
2022-04-06 18:17:33 +02:00
}
}
2022-04-06 17:51:06 +02:00
}
2022-04-06 16:52:31 +02:00
2022-04-06 17:51:06 +02:00
$ for field in T . fields {
2022-04-13 14:51:01 +02:00
env_value := get_env_var ( field . name ) ?
2022-04-06 17:51:06 +02:00
2022-04-13 14:51:01 +02:00
// The value of an env var will always take precedence over the toml
// file .
if env_value != ' ' {
$ if field . typ is string {
2022-04-06 17:51:06 +02:00
res . $ ( field . name ) = env_value
2022-04-13 14:51:01 +02:00
} $ else $ if field . typ is int {
res . $ ( field . name ) = env_value . int ()
2022-04-06 17:51:06 +02:00
}
2022-04-13 14:51:01 +02:00
}
// Now , we check whether a value is present . If there isn't , that means
// it isn't in the config file , nor is there a default or an env var .
mut has_value := false
$ if field . typ is string {
has_value = res . $ ( field . name ) != ' '
} $ else $ if field . typ is int {
has_value = res . $ ( field . name ) != 0
}
if ! has_value {
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. " )
2022-04-06 17:51:06 +02:00
}
}
2022-02-21 20:40:13 +01:00
return res
}