From 44aa7c9dc95cb6a47cd904621fdd212063d0d8cb Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Wed, 28 Dec 2022 17:39:20 +0100 Subject: [PATCH 1/7] test: improve testing quality --- conf.v | 28 ++++++++++++++++++++++++---- string_test.v | 35 +++++++++++++++++++++++++++++++++++ test/empty.toml | 0 test/string.toml | 1 + 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 string_test.v create mode 100644 test/empty.toml create mode 100644 test/string.toml diff --git a/conf.v b/conf.v index c3865ca..3ebc194 100644 --- a/conf.v +++ b/conf.v @@ -2,6 +2,7 @@ module conf import os import toml +import datatypes { Set } // get_env_var tries to read the contents of the given environment variable. It // looks for either `${env.prefix}${field_name.to_upper()}` or @@ -54,7 +55,7 @@ pub fn load(conf LoadConfig) !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{} + mut has_value := Set{} // Later, this could be read from an env var as well. path := conf.default_path @@ -77,7 +78,7 @@ pub fn load(conf LoadConfig) !T { // $compile_error('Unsupported config struct field type detected.') } - has_value[field.name] = true + has_value.add(field.name) } } } @@ -97,12 +98,31 @@ pub fn load(conf LoadConfig) !T { // $compile_error('Unsupported config struct field type detected.') } - has_value[field.name] = true + has_value.add(field.name) } - if !(has_value[field.name] or { false }) { + // Finally, if there's no env var present either, we check whether the + // variable has a default value + 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 } diff --git a/string_test.v b/string_test.v new file mode 100644 index 0000000..c498b11 --- /dev/null +++ b/string_test.v @@ -0,0 +1,35 @@ +module conf + +struct SingleConf { + some_string string +} + +struct SingleConfDefault { + some_string string = 'default' +} + +fn test_string_present_no_default() { + conf := load(default_path: 'test/string.toml')! + assert conf == SingleConf{ + some_string: 'hi' + } +} + +fn test_string_absent_no_default() { + conf := load(default_path: 'test/empty.toml') or { return } + assert false +} + +fn test_string_present_default() { + conf := load(default_path: 'test/string.toml')! + assert conf == SingleConfDefault{ + some_string: 'hi' + } +} + +fn test_string_absent_default() { + conf := load(default_path: 'test/empty.toml')! + assert conf == SingleConfDefault{ + some_string: 'default' + } +} diff --git a/test/empty.toml b/test/empty.toml new file mode 100644 index 0000000..e69de29 diff --git a/test/string.toml b/test/string.toml new file mode 100644 index 0000000..85c27d0 --- /dev/null +++ b/test/string.toml @@ -0,0 +1 @@ +some_string = "hi" From 997e9611eb7a397d8bccb35b9a2545d735f7625c Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 18:13:20 +0100 Subject: [PATCH 2/7] test: also test env vars --- conf.v | 37 ++++++++++++++--------------- string_test.v | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/conf.v b/conf.v index 3ebc194..c7a0e0d 100644 --- a/conf.v +++ b/conf.v @@ -4,16 +4,25 @@ 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. -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) +fn (ld LoadConfig) get_env_var(field_name string) !string { + env_var_name := '$ld.prefix$field_name.to_upper()' + env_file_name := '$ld.prefix$field_name.to_upper()$ld.file_suffix' + env_var := ld.env[env_var_name] or { '' } + env_file := ld.env[env_file_name] or { '' } // If both are missing, we return an empty string if env_var == '' && env_file == '' { @@ -39,18 +48,11 @@ fn get_env_var(prefix string, field_name string, file_suffix string) !string { } } -[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(conf LoadConfig) !T { +pub fn load(ld LoadConfig) !T { mut res := T{} // This array allows us to determine later whether the variable is actually @@ -58,7 +60,7 @@ pub fn load(conf LoadConfig) !T { mut has_value := Set{} // Later, this could be read from an env var as well. - path := conf.default_path + path := ld.default_path if os.exists(path) { // We don't use reflect here because reflect also sets any fields not @@ -84,7 +86,7 @@ pub fn load(conf LoadConfig) !T { } $for field in T.fields { - env_value := get_env_var(conf.prefix, field.name, conf.file_suffix)! + env_value := ld.get_env_var(field.name)! // The value of an env var will always take precedence over the toml // file. @@ -117,12 +119,11 @@ pub fn load(conf LoadConfig) !T { } } - // If there's no value provided in any way, we notify the user with an - // error. + // 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 } diff --git a/string_test.v b/string_test.v index c498b11..b91cff5 100644 --- a/string_test.v +++ b/string_test.v @@ -8,6 +8,14 @@ struct SingleConfDefault { some_string string = 'default' } +const env = { + 'SOME_STRING': 'env' +} + +const prefix_env = { + 'TEST_SOME_STRING': 'env' +} + fn test_string_present_no_default() { conf := load(default_path: 'test/string.toml')! assert conf == SingleConf{ @@ -15,11 +23,35 @@ fn test_string_present_no_default() { } } +fn test_string_present_no_default_env() { + conf := load(default_path: 'test/string.toml', env: .env)! + assert conf == SingleConf{ + some_string: 'env' + } + + conf2 := load(default_path: 'test/string.toml', env: .prefix_env, prefix: 'TEST_')! + assert conf2 == 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() { + conf := load(default_path: 'test/string.toml', env: .env)! + assert conf == SingleConf{ + some_string: 'env' + } + + conf2 := load(default_path: 'test/string.toml', env: .prefix_env, prefix: 'TEST_')! + assert conf2 == SingleConf{ + some_string: 'env' + } +} + fn test_string_present_default() { conf := load(default_path: 'test/string.toml')! assert conf == SingleConfDefault{ @@ -27,9 +59,41 @@ fn test_string_present_default() { } } +fn test_string_present_default_env() { + conf := load(default_path: 'test/string.toml', env: .env)! + assert conf == SingleConfDefault{ + some_string: 'env' + } + + conf2 := load( + default_path: 'test/string.toml' + env: .prefix_env + prefix: 'TEST_' + )! + assert conf2 == 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() { + conf := load(default_path: 'test/empty.toml', env: .env)! + assert conf == SingleConfDefault{ + some_string: 'env' + } + + conf2 := load( + default_path: 'test/empty.toml' + env: .prefix_env + prefix: 'TEST_' + )! + assert conf2 == SingleConfDefault{ + some_string: 'env' + } +} From 6678040d30b0b5682e6b7ec5c260c58be8ff9f87 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 18:36:58 +0100 Subject: [PATCH 3/7] feat: allow setting empty env var value --- conf.v | 24 ++++----- string_test.v | 117 +++++++++++++++++++++++++++++++++-------- test/int.toml | 1 + test/string_empty.toml | 1 + 4 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 test/int.toml create mode 100644 test/string_empty.toml diff --git a/conf.v b/conf.v index c7a0e0d..65c0a16 100644 --- a/conf.v +++ b/conf.v @@ -17,20 +17,18 @@ pub struct LoadConfig { // 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. -fn (ld LoadConfig) get_env_var(field_name string) !string { +// 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' - env_var := ld.env[env_var_name] or { '' } - env_file := ld.env[env_file_name] or { '' } - // If both are missing, we return an empty string - if env_var == '' && env_file == '' { - return '' + 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 != '' && env_file != '' { + 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.') } @@ -38,12 +36,12 @@ fn (ld LoadConfig) get_env_var(field_name string) !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 != '' { - return env_var + if env_var_name in ld.env { + return true, ld.env[env_var_name] } // Otherwise, we process the file - return os.read_file(env_file) or { + return true, os.read_file(ld.env[env_file_name]) or { error('Failed to read file defined in $env_file_name: ${err.msg()}.') } } @@ -86,11 +84,11 @@ pub fn load(ld LoadConfig) !T { } $for field in T.fields { - env_value := ld.get_env_var(field.name)! + 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_value != '' { + if env_present { $if field.typ is string { res.$(field.name) = env_value } $else $if field.typ is int { diff --git a/string_test.v b/string_test.v index b91cff5..46cb85d 100644 --- a/string_test.v +++ b/string_test.v @@ -8,29 +8,47 @@ struct SingleConfDefault { some_string string = 'default' } -const env = { - 'SOME_STRING': 'env' -} - -const prefix_env = { - 'TEST_SOME_STRING': 'env' -} - fn test_string_present_no_default() { conf := load(default_path: 'test/string.toml')! assert conf == SingleConf{ some_string: 'hi' } + + conf2 := load(default_path: 'test/string_empty.toml')! + assert conf2 == SingleConf{ + some_string: '' + } } fn test_string_present_no_default_env() { - conf := load(default_path: 'test/string.toml', env: .env)! + mut conf := load( + default_path: 'test/string.toml' + env: { + 'SOME_STRING': 'env' + } + )! assert conf == SingleConf{ some_string: 'env' } - conf2 := load(default_path: 'test/string.toml', env: .prefix_env, prefix: 'TEST_')! - assert conf2 == SingleConf{ + 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' } } @@ -41,13 +59,34 @@ fn test_string_absent_no_default() { } fn test_string_absent_no_default_env() { - conf := load(default_path: 'test/string.toml', env: .env)! + mut conf := load( + default_path: 'test/string.toml' + env: { + 'SOME_STRING': 'env' + } + )! assert conf == SingleConf{ some_string: 'env' } - conf2 := load(default_path: 'test/string.toml', env: .prefix_env, prefix: 'TEST_')! - assert conf2 == SingleConf{ + 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' } } @@ -60,17 +99,34 @@ fn test_string_present_default() { } fn test_string_present_default_env() { - conf := load(default_path: 'test/string.toml', env: .env)! + mut conf := load( + default_path: 'test/string.toml' + env: { + 'SOME_STRING': 'env' + } + )! assert conf == SingleConfDefault{ some_string: 'env' } - conf2 := load( + conf = load( default_path: 'test/string.toml' - env: .prefix_env + env: { + 'SOME_STRING': '' + } + )! + assert conf == SingleConfDefault{ + some_string: '' + } + + conf = load( + default_path: 'test/string.toml' + env: { + 'TEST_SOME_STRING': 'env' + } prefix: 'TEST_' )! - assert conf2 == SingleConfDefault{ + assert conf == SingleConfDefault{ some_string: 'env' } } @@ -83,17 +139,34 @@ fn test_string_absent_default() { } fn test_string_absent_default_env() { - conf := load(default_path: 'test/empty.toml', env: .env)! + mut conf := load( + default_path: 'test/empty.toml' + env: { + 'SOME_STRING': 'env' + } + )! assert conf == SingleConfDefault{ some_string: 'env' } - conf2 := load( + conf = load( default_path: 'test/empty.toml' - env: .prefix_env + env: { + 'SOME_STRING': '' + } + )! + assert conf == SingleConfDefault{ + some_string: '' + } + + conf = load( + default_path: 'test/empty.toml' + env: { + 'TEST_SOME_STRING': 'env' + } prefix: 'TEST_' )! - assert conf2 == SingleConfDefault{ + assert conf == SingleConfDefault{ some_string: 'env' } } diff --git a/test/int.toml b/test/int.toml new file mode 100644 index 0000000..263b0ce --- /dev/null +++ b/test/int.toml @@ -0,0 +1 @@ +some_int = "hi" diff --git a/test/string_empty.toml b/test/string_empty.toml new file mode 100644 index 0000000..8866b17 --- /dev/null +++ b/test/string_empty.toml @@ -0,0 +1 @@ +some_string = "" From 8bd7ce0b605a64bf363a7fa1e8fbb71e16b736dc Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 18:50:12 +0100 Subject: [PATCH 4/7] feat: allow "zeroed" values as defaults --- conf.v | 7 +++++-- string_test.v | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/conf.v b/conf.v index 65c0a16..0cb2250 100644 --- a/conf.v +++ b/conf.v @@ -102,8 +102,11 @@ pub fn load(ld LoadConfig) !T { } // Finally, if there's no env var present either, we check whether the - // variable has a default value - if !has_value.exists(field.name) { + // 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 { diff --git a/string_test.v b/string_test.v index 46cb85d..9dfc191 100644 --- a/string_test.v +++ b/string_test.v @@ -170,3 +170,14 @@ fn test_string_absent_default_env() { 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: '' + } +} From a1ab5d55b3d71ee22df56bec8ca7cd1430865f9d Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 18:57:10 +0100 Subject: [PATCH 5/7] test: expand testing to ints --- conf_test.v | 43 --------- int_test.v | 183 ++++++++++++++++++++++++++++++++++++ string_test.v | 6 +- test/int.toml | 2 +- test/int_zero.toml | 1 + test/test_simple.toml | 2 - test/test_single_value.toml | 1 - test/test_zeroed.toml | 2 - 8 files changed, 188 insertions(+), 52 deletions(-) delete mode 100644 conf_test.v create mode 100644 int_test.v create mode 100644 test/int_zero.toml delete mode 100644 test/test_simple.toml delete mode 100644 test/test_single_value.toml delete mode 100644 test/test_zeroed.toml diff --git a/conf_test.v b/conf_test.v deleted file mode 100644 index 72e7703..0000000 --- a/conf_test.v +++ /dev/null @@ -1,43 +0,0 @@ -module conf - -struct SimpleConf { - some_int int - some_string string -} - -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 new file mode 100644 index 0000000..e248319 --- /dev/null +++ b/int_test.v @@ -0,0 +1,183 @@ +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 + } +} diff --git a/string_test.v b/string_test.v index 9dfc191..9c7a6a9 100644 --- a/string_test.v +++ b/string_test.v @@ -9,13 +9,13 @@ struct SingleConfDefault { } fn test_string_present_no_default() { - conf := load(default_path: 'test/string.toml')! + mut conf := load(default_path: 'test/string.toml')! assert conf == SingleConf{ some_string: 'hi' } - conf2 := load(default_path: 'test/string_empty.toml')! - assert conf2 == SingleConf{ + conf = load(default_path: 'test/string_empty.toml')! + assert conf == SingleConf{ some_string: '' } } diff --git a/test/int.toml b/test/int.toml index 263b0ce..b8326e0 100644 --- a/test/int.toml +++ b/test/int.toml @@ -1 +1 @@ -some_int = "hi" +some_int = 1 diff --git a/test/int_zero.toml b/test/int_zero.toml new file mode 100644 index 0000000..4dd4d46 --- /dev/null +++ b/test/int_zero.toml @@ -0,0 +1 @@ +some_int = 0 diff --git a/test/test_simple.toml b/test/test_simple.toml deleted file mode 100644 index 489eea3..0000000 --- a/test/test_simple.toml +++ /dev/null @@ -1,2 +0,0 @@ -some_int = 2 -some_string = "hi" diff --git a/test/test_single_value.toml b/test/test_single_value.toml deleted file mode 100644 index 85c27d0..0000000 --- a/test/test_single_value.toml +++ /dev/null @@ -1 +0,0 @@ -some_string = "hi" diff --git a/test/test_zeroed.toml b/test/test_zeroed.toml deleted file mode 100644 index ce4dd51..0000000 --- a/test/test_zeroed.toml +++ /dev/null @@ -1,2 +0,0 @@ -some_int = 0 -some_string = "" From 5fd74631eb7ba46069cd963ce170b47d7dbfbd3b Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 19:43:10 +0100 Subject: [PATCH 6/7] feat: prevent fields with invalid types --- conf.v | 17 +++++++++++------ conf_test.v | 10 ++++++++++ int_test.v | 5 +++++ test/int_wrong_type.toml | 1 + 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 conf_test.v create mode 100644 test/int_wrong_type.toml diff --git a/conf.v b/conf.v index 0cb2250..f92361d 100644 --- a/conf.v +++ b/conf.v @@ -51,6 +51,17 @@ fn (ld LoadConfig) get_env_var(field_name string) !(bool, string) { // 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 { + } $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.') + } + } mut res := T{} // This array allows us to determine later whether the variable is actually @@ -73,9 +84,6 @@ pub fn load(ld LoadConfig) !T { 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) @@ -93,9 +101,6 @@ pub fn load(ld LoadConfig) !T { 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) diff --git a/conf_test.v b/conf_test.v new file mode 100644 index 0000000..edc985f --- /dev/null +++ b/conf_test.v @@ -0,0 +1,10 @@ +module conf + +struct WrongTypeConfig { + f map[string]string +} + +fn test_wrong_type() { + conf := load() or { return } + assert false +} diff --git a/int_test.v b/int_test.v index e248319..f02ef9e 100644 --- a/int_test.v +++ b/int_test.v @@ -181,3 +181,8 @@ fn test_int_absent_default_empty() { some_int: 0 } } + +/* fn test_int_wrong_type() { */ +/* conf := load(default_path: 'test/int_wrong_type.toml') or { return } */ +/* assert false */ +/* } */ diff --git a/test/int_wrong_type.toml b/test/int_wrong_type.toml new file mode 100644 index 0000000..e63b96b --- /dev/null +++ b/test/int_wrong_type.toml @@ -0,0 +1 @@ +some_int = '1' From fca58e3da016fe9d0936022e6bfcac1e86af0945 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 28 Dec 2022 20:04:45 +0100 Subject: [PATCH 7/7] feat: add bool type --- bool_test.v | 171 +++++++++++++++++++++++++++++++++++++++++++++++++ conf.v | 12 +++- conf_test.v | 2 +- int_test.v | 8 +-- test/bool.toml | 1 + 5 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 bool_test.v create mode 100644 test/bool.toml diff --git a/bool_test.v b/bool_test.v new file mode 100644 index 0000000..1418bde --- /dev/null +++ b/bool_test.v @@ -0,0 +1,171 @@ +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 f92361d..cf2405e 100644 --- a/conf.v +++ b/conf.v @@ -53,7 +53,7 @@ fn (ld LoadConfig) get_env_var(field_name string) !(bool, string) { 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 { + $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 @@ -84,6 +84,8 @@ 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() } has_value.add(field.name) @@ -101,6 +103,10 @@ pub fn load(ld LoadConfig) !T { 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'] } has_value.add(field.name) @@ -118,6 +124,10 @@ pub fn load(ld LoadConfig) !T { 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 { diff --git a/conf_test.v b/conf_test.v index edc985f..4877724 100644 --- a/conf_test.v +++ b/conf_test.v @@ -1,7 +1,7 @@ module conf struct WrongTypeConfig { - f map[string]string + f map[string]string } fn test_wrong_type() { diff --git a/int_test.v b/int_test.v index f02ef9e..defddc8 100644 --- a/int_test.v +++ b/int_test.v @@ -182,7 +182,7 @@ fn test_int_absent_default_empty() { } } -/* fn test_int_wrong_type() { */ -/* conf := load(default_path: 'test/int_wrong_type.toml') or { return } */ -/* assert false */ -/* } */ +// fn test_int_wrong_type() { +// conf := load(default_path: 'test/int_wrong_type.toml') or { return } +// assert false +//} diff --git a/test/bool.toml b/test/bool.toml new file mode 100644 index 0000000..a3e45e2 --- /dev/null +++ b/test/bool.toml @@ -0,0 +1 @@ +some_bool = true