diff --git a/bool_test.v b/bool_test.v deleted file mode 100644 index 1418bde..0000000 --- a/bool_test.v +++ /dev/null @@ -1,171 +0,0 @@ -module conf - -struct SingleConf { - some_bool bool -} - -struct SingleConfDefaultFalse { - some_bool bool [empty_default] -} - -struct SingleConfDefaultTrue { - some_bool bool = true -} - -fn test_bool_present_no_default() { - mut conf := load(default_path: 'test/bool.toml')! - assert conf == SingleConf{ - some_bool: true - } -} - -fn test_bool_present_no_default_env() { - mut conf_f := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '1' - } - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } - - mut conf_t := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '' - } - )! - assert conf_t == SingleConfDefaultTrue{ - some_bool: false - } - - conf_f = load( - default_path: 'test/bool.toml' - env: { - 'TEST_SOME_BOOL': 'true' - } - prefix: 'TEST_' - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } -} - -fn test_bool_absent_no_default() { - conf := load(default_path: 'test/empty.toml') or { return } - assert false -} - -fn test_bool_absent_no_default_env() { - mut conf_f := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '1' - } - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } - - mut conf_t := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '' - } - )! - assert conf_t == SingleConfDefaultTrue{ - some_bool: false - } - - conf_f = load( - default_path: 'test/bool.toml' - env: { - 'TEST_SOME_BOOL': '1' - } - prefix: 'TEST_' - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } -} - -fn test_bool_present_default() { - conf := load(default_path: 'test/bool.toml')! - assert conf == SingleConfDefaultFalse{ - some_bool: true - } -} - -fn test_bool_present_default_env() { - mut conf_f := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '1' - } - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } - - mut conf_t := load( - default_path: 'test/bool.toml' - env: { - 'SOME_BOOL': '' - } - )! - assert conf_t == SingleConfDefaultTrue{ - some_bool: false - } - - conf_f = load( - default_path: 'test/bool.toml' - env: { - 'TEST_SOME_BOOL': '1' - } - prefix: 'TEST_' - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } -} - -fn test_bool_absent_default() { - conf := load(default_path: 'test/empty.toml')! - assert conf == SingleConfDefaultTrue{ - some_bool: true - } -} - -fn test_bool_absent_default_env() { - mut conf_f := load( - default_path: 'test/empty.toml' - env: { - 'SOME_BOOL': '1' - } - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } - - mut conf_t := load( - default_path: 'test/empty.toml' - env: { - 'SOME_BOOL': '' - } - )! - assert conf_t == SingleConfDefaultTrue{ - some_bool: false - } - - conf_f = load( - default_path: 'test/empty.toml' - env: { - 'TEST_SOME_BOOL': 'true' - } - prefix: 'TEST_' - )! - assert conf_f == SingleConfDefaultFalse{ - some_bool: true - } -} diff --git a/conf.v b/conf.v index cf2405e..c3865ca 100644 --- a/conf.v +++ b/conf.v @@ -2,33 +2,25 @@ 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' +// function returns an error. +fn get_env_var(prefix string, field_name string, file_suffix string) !string { + 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 env_var_name !in ld.env && env_file_name !in ld.env { - return false, '' + // 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_name in ld.env && env_file_name in ld.env { + if env_var != '' && env_file != '' { return error('Only one of $env_var_name or $env_file_name can be defined.') } @@ -36,40 +28,36 @@ fn (ld LoadConfig) get_env_var(field_name string) !(bool, string) { // 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] + if env_var != '' { + return env_var } // Otherwise, we process the file - return true, os.read_file(ld.env[env_file_name]) or { + return os.read_file(env_file) or { error('Failed to read file defined in $env_file_name: ${err.msg()}.') } } +[params] +pub struct LoadConfig { + prefix string + file_suffix string = '_FILE' + default_path string +} + // load 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(ld LoadConfig) !T { - // Ensure all struct fields consist of supported types - $for field in T.fields { - $if field.typ is string || field.typ is int || field.typ is bool { - } $else { - // I'd prefer changing this to $compile_error, but as of V 0.3.2, - // this seems to be bugged. If I replace this call with a - // $compile_error call, the error *always* happens, even if all - // fields are correct. - return error('Field $field.name is of an unsupported type.') - } - } +pub fn load(conf 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{} + mut has_value := map[string]bool{} // Later, this could be read from an env var as well. - path := ld.default_path + path := conf.default_path if os.exists(path) { // We don't use reflect here because reflect also sets any fields not @@ -84,60 +72,35 @@ pub fn load(ld LoadConfig) !T { res.$(field.name) = s.string() } $else $if field.typ is int { res.$(field.name) = s.int() - } $else $if field.typ is bool { - res.$(field.name) = s.bool() + // This seems to not work in V 0.3.2 + //} $else { + // $compile_error('Unsupported config struct field type detected.') } - has_value.add(field.name) + has_value[field.name] = true } } } $for field in T.fields { - env_present, env_value := ld.get_env_var(field.name)! + env_value := get_env_var(conf.prefix, field.name, conf.file_suffix)! // The value of an env var will always take precedence over the toml // file. - if env_present { + 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() - } $else $if field.typ is bool { - // Env var accepts '1' and 'true' as truthy, everything else - // evaluates to false - res.$(field.name) = env_value in ['1', 'true'] + // This seems to not work in V 0.3.2 + //} $else { + // $compile_error('Unsupported config struct field type detected.') } - has_value.add(field.name) + has_value[field.name] = true } - // 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 - } $else $if field.typ is bool { - // This explicit comparison is required as the type system gets - // a bit confused otherwise - has_default = res.$(field.name) == true - } - - 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) { + if !(has_value[field.name] or { false }) { 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.") } } diff --git a/conf_test.v b/conf_test.v index 4877724..72e7703 100644 --- a/conf_test.v +++ b/conf_test.v @@ -1,10 +1,43 @@ module conf -struct WrongTypeConfig { - f map[string]string +struct SimpleConf { + some_int int + some_string string } -fn test_wrong_type() { - conf := load() or { return } - assert false +struct SimpleConfDefaults { + some_int int = 3 + some_string string = 'hi' +} + +fn test_simple() { + conf := load(default_path: 'test/test_simple.toml')! + assert conf == SimpleConf{ + some_int: 2 + some_string: 'hi' + } +} + +fn test_zeroed() { + conf := load(default_path: 'test/test_zeroed.toml')! + assert conf == SimpleConf{ + some_int: 0 + some_string: '' + } +} + +fn test_zeroed_defaults() { + conf := load(default_path: 'test/test_zeroed.toml')! + assert conf == SimpleConfDefaults{ + some_int: 0 + some_string: '' + } +} + +fn test_defaults() { + conf := load(default_path: 'test/test_single_value.toml')! + assert conf == SimpleConfDefaults{ + some_int: 3 + some_string: 'hi' + } } diff --git a/int_test.v b/int_test.v deleted file mode 100644 index defddc8..0000000 --- a/int_test.v +++ /dev/null @@ -1,188 +0,0 @@ -module conf - -struct SingleConf { - some_int int -} - -struct SingleConfDefault { - some_int int = 2 -} - -fn test_int_present_no_default() { - mut conf := load(default_path: 'test/int.toml')! - assert conf == SingleConf{ - some_int: 1 - } - - conf = load(default_path: 'test/int_zero.toml')! - assert conf == SingleConf{ - some_int: 0 - } -} - -fn test_int_present_no_default_env() { - mut conf := load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '3' - } - )! - assert conf == SingleConf{ - some_int: 3 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '' - } - )! - assert conf == SingleConf{ - some_int: 0 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'TEST_SOME_INT': '3' - } - prefix: 'TEST_' - )! - assert conf == SingleConf{ - some_int: 3 - } -} - -fn test_int_absent_no_default() { - conf := load(default_path: 'test/empty.toml') or { return } - assert false -} - -fn test_int_absent_no_default_env() { - mut conf := load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '3' - } - )! - assert conf == SingleConf{ - some_int: 3 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '' - } - )! - assert conf == SingleConf{ - some_int: 0 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'TEST_SOME_INT': '3' - } - prefix: 'TEST_' - )! - assert conf == SingleConf{ - some_int: 3 - } -} - -fn test_int_present_default() { - conf := load(default_path: 'test/int.toml')! - assert conf == SingleConfDefault{ - some_int: 1 - } -} - -fn test_int_present_default_env() { - mut conf := load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '3' - } - )! - assert conf == SingleConfDefault{ - some_int: 3 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'SOME_INT': '' - } - )! - assert conf == SingleConfDefault{ - some_int: 0 - } - - conf = load( - default_path: 'test/int.toml' - env: { - 'TEST_SOME_INT': '3' - } - prefix: 'TEST_' - )! - assert conf == SingleConfDefault{ - some_int: 3 - } -} - -fn test_int_absent_default() { - conf := load(default_path: 'test/empty.toml')! - assert conf == SingleConfDefault{ - some_int: 2 - } -} - -fn test_int_absent_default_env() { - mut conf := load( - default_path: 'test/empty.toml' - env: { - 'SOME_INT': '3' - } - )! - assert conf == SingleConfDefault{ - some_int: 3 - } - - conf = load( - default_path: 'test/empty.toml' - env: { - 'SOME_INT': '' - } - )! - assert conf == SingleConfDefault{ - some_int: 0 - } - - conf = load( - default_path: 'test/empty.toml' - env: { - 'TEST_SOME_INT': '3' - } - prefix: 'TEST_' - )! - assert conf == SingleConfDefault{ - some_int: 3 - } -} - -struct SingleConfDefaultEmpty { - some_int int [empty_default] -} - -fn test_int_absent_default_empty() { - conf := load(default_path: 'test/empty.toml')! - assert conf == SingleConfDefaultEmpty{ - some_int: 0 - } -} - -// fn test_int_wrong_type() { -// conf := load(default_path: 'test/int_wrong_type.toml') or { return } -// assert false -//} diff --git a/string_test.v b/string_test.v deleted file mode 100644 index 9c7a6a9..0000000 --- a/string_test.v +++ /dev/null @@ -1,183 +0,0 @@ -module conf - -struct SingleConf { - some_string string -} - -struct SingleConfDefault { - some_string string = 'default' -} - -fn test_string_present_no_default() { - mut conf := load(default_path: 'test/string.toml')! - assert conf == SingleConf{ - some_string: 'hi' - } - - conf = load(default_path: 'test/string_empty.toml')! - assert conf == SingleConf{ - some_string: '' - } -} - -fn test_string_present_no_default_env() { - mut conf := load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': 'env' - } - )! - assert conf == SingleConf{ - some_string: 'env' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': '' - } - )! - assert conf == SingleConf{ - some_string: '' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'TEST_SOME_STRING': 'env' - } - prefix: 'TEST_' - )! - assert conf == SingleConf{ - some_string: 'env' - } -} - -fn test_string_absent_no_default() { - conf := load(default_path: 'test/empty.toml') or { return } - assert false -} - -fn test_string_absent_no_default_env() { - mut conf := load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': 'env' - } - )! - assert conf == SingleConf{ - some_string: 'env' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': '' - } - )! - assert conf == SingleConf{ - some_string: '' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'TEST_SOME_STRING': 'env' - } - prefix: 'TEST_' - )! - assert conf == SingleConf{ - some_string: 'env' - } -} - -fn test_string_present_default() { - conf := load(default_path: 'test/string.toml')! - assert conf == SingleConfDefault{ - some_string: 'hi' - } -} - -fn test_string_present_default_env() { - mut conf := load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': 'env' - } - )! - assert conf == SingleConfDefault{ - some_string: 'env' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'SOME_STRING': '' - } - )! - assert conf == SingleConfDefault{ - some_string: '' - } - - conf = load( - default_path: 'test/string.toml' - env: { - 'TEST_SOME_STRING': 'env' - } - prefix: 'TEST_' - )! - assert conf == SingleConfDefault{ - some_string: 'env' - } -} - -fn test_string_absent_default() { - conf := load(default_path: 'test/empty.toml')! - assert conf == SingleConfDefault{ - some_string: 'default' - } -} - -fn test_string_absent_default_env() { - mut conf := load( - default_path: 'test/empty.toml' - env: { - 'SOME_STRING': 'env' - } - )! - assert conf == SingleConfDefault{ - some_string: 'env' - } - - conf = load( - default_path: 'test/empty.toml' - env: { - 'SOME_STRING': '' - } - )! - assert conf == SingleConfDefault{ - some_string: '' - } - - conf = load( - default_path: 'test/empty.toml' - env: { - 'TEST_SOME_STRING': 'env' - } - prefix: 'TEST_' - )! - assert conf == SingleConfDefault{ - some_string: 'env' - } -} - -struct SingleConfDefaultEmpty { - some_string string [empty_default] -} - -fn test_string_absent_default_empty() { - conf := load(default_path: 'test/empty.toml')! - assert conf == SingleConfDefaultEmpty{ - some_string: '' - } -} diff --git a/test/bool.toml b/test/bool.toml deleted file mode 100644 index a3e45e2..0000000 --- a/test/bool.toml +++ /dev/null @@ -1 +0,0 @@ -some_bool = true diff --git a/test/empty.toml b/test/empty.toml deleted file mode 100644 index e69de29..0000000 diff --git a/test/int.toml b/test/int.toml deleted file mode 100644 index b8326e0..0000000 --- a/test/int.toml +++ /dev/null @@ -1 +0,0 @@ -some_int = 1 diff --git a/test/int_wrong_type.toml b/test/int_wrong_type.toml deleted file mode 100644 index e63b96b..0000000 --- a/test/int_wrong_type.toml +++ /dev/null @@ -1 +0,0 @@ -some_int = '1' diff --git a/test/int_zero.toml b/test/int_zero.toml deleted file mode 100644 index 4dd4d46..0000000 --- a/test/int_zero.toml +++ /dev/null @@ -1 +0,0 @@ -some_int = 0 diff --git a/test/test_simple.toml b/test/test_simple.toml new file mode 100644 index 0000000..489eea3 --- /dev/null +++ b/test/test_simple.toml @@ -0,0 +1,2 @@ +some_int = 2 +some_string = "hi" diff --git a/test/string.toml b/test/test_single_value.toml similarity index 100% rename from test/string.toml rename to test/test_single_value.toml diff --git a/test/string_empty.toml b/test/test_zeroed.toml similarity index 56% rename from test/string_empty.toml rename to test/test_zeroed.toml index 8866b17..ce4dd51 100644 --- a/test/string_empty.toml +++ b/test/test_zeroed.toml @@ -1 +1,2 @@ +some_int = 0 some_string = ""