@@ -19,21 +19,34 @@ const ENV_PREFIX_SEPARATOR: &str = "_";
19
19
/// Example: `APP_DATABASE__URL` sets the `database.url` field.
20
20
const ENV_SEPARATOR : & str = "__" ;
21
21
22
+ /// Separator for list elements in environment variables.
23
+ ///
24
+ /// Example: `APP_API_KEYS=abc,def` sets the `api_keys` array field.
25
+ const LIST_SEPARATOR : & str = "," ;
26
+
27
+ /// Trait defining the list of keys that should be parsed as lists in a given [`Config`]
28
+ /// implementation.
29
+ pub trait Config {
30
+ /// Slice containing all the keys that should be parsed as lists when loading the configuration.
31
+ const LIST_PARSE_KEYS : & ' static [ & ' static str ] ;
32
+ }
33
+
22
34
/// Loads hierarchical configuration from YAML files and environment variables.
23
35
///
24
36
/// Loads configuration in this order:
25
37
/// 1. Base configuration from `configuration/base.yaml`
26
38
/// 2. Environment-specific file from `configuration/{environment}.yaml`
27
39
/// 3. Environment variable overrides prefixed with `APP`
28
40
///
29
- /// Nested keys use double underscores: `APP_DATABASE__URL` → `database.url`
41
+ /// Nested keys use double underscores: `APP_DATABASE__URL` → `database.url` and lists are separated
42
+ /// by `,`.
30
43
///
31
44
/// # Panics
32
45
/// Panics if current directory cannot be determined or if `APP_ENVIRONMENT`
33
46
/// cannot be parsed.
34
47
pub fn load_config < T > ( ) -> Result < T , config:: ConfigError >
35
48
where
36
- T : DeserializeOwned ,
49
+ T : Config + DeserializeOwned ,
37
50
{
38
51
let base_path = std:: env:: current_dir ( ) . expect ( "Failed to determine the current directory" ) ;
39
52
let configuration_directory = base_path. join ( CONFIGURATION_DIR ) ;
@@ -43,21 +56,33 @@ where
43
56
let environment = Environment :: load ( ) . expect ( "Failed to parse APP_ENVIRONMENT." ) ;
44
57
45
58
let environment_filename = format ! ( "{environment}.yaml" ) ;
59
+
60
+ // We build the environment configuration source.
61
+ let mut environment_source = config:: Environment :: with_prefix ( ENV_PREFIX )
62
+ . prefix_separator ( ENV_PREFIX_SEPARATOR )
63
+ . separator ( ENV_SEPARATOR )
64
+ . try_parsing ( true )
65
+ . list_separator ( LIST_SEPARATOR ) ;
66
+
67
+ // For all the list parse keys, we add them to the environment source. These are used to define
68
+ // which keys should be parsed as lists.
69
+ for key in <T as Config >:: LIST_PARSE_KEYS {
70
+ environment_source = environment_source. with_list_parse_key ( key) ;
71
+ }
72
+
46
73
let settings = config:: Config :: builder ( )
74
+ // Add in settings from the base configuration file.
47
75
. add_source ( config:: File :: from (
48
76
configuration_directory. join ( BASE_CONFIG_FILE ) ,
49
77
) )
78
+ // Add in settings from the environment-specific file.
50
79
. add_source ( config:: File :: from (
51
80
configuration_directory. join ( environment_filename) ,
52
81
) )
53
82
// Add in settings from environment variables (with a prefix of APP and '__' as separator)
54
83
// E.g. `APP_DESTINATION__BIG_QUERY__PROJECT_ID=my-project-id` sets
55
84
// `Settings { destination: BigQuery { project_id } }` to `my-project-id`.
56
- . add_source (
57
- config:: Environment :: with_prefix ( ENV_PREFIX )
58
- . prefix_separator ( ENV_PREFIX_SEPARATOR )
59
- . separator ( ENV_SEPARATOR ) ,
60
- )
85
+ . add_source ( environment_source)
61
86
. build ( ) ?;
62
87
63
88
settings. try_deserialize :: < T > ( )
0 commit comments