diff --git a/CHANGELOG.md b/CHANGELOG.md index c350752..6caa3ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +#### Unreleased +* [breaking] Nested config fields now needs to be marked with #[envconfig(nested = true)] +* Environment variable can be automatically derived from a field name (e.g. `db_host` will be tried to loaded from `DB_HOST` env var) + #### v0.9.1 - 2019-10-09 * Get rid of thiserror dependency diff --git a/envconfig/src/error.rs b/envconfig/src/error.rs index 35a0f22..2275930 100644 --- a/envconfig/src/error.rs +++ b/envconfig/src/error.rs @@ -21,6 +21,6 @@ impl fmt::Display for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - return None; + None } } diff --git a/envconfig/src/lib.rs b/envconfig/src/lib.rs index 4c52e33..f0bc759 100644 --- a/envconfig/src/lib.rs +++ b/envconfig/src/lib.rs @@ -20,18 +20,16 @@ //! pub http_port: u16, //! } //! -//! fn main() { -//! // We assume that those environment variables are set somewhere outside -//! env::set_var("DB_HOST", "localhost"); -//! env::set_var("DB_PORT", "5432"); +//! // We assume that those environment variables are set somewhere outside +//! env::set_var("DB_HOST", "localhost"); +//! env::set_var("DB_PORT", "5432"); //! -//! // Initialize config from environment variables -//! let config = Config::init().unwrap(); +//! // Initialize config from environment variables +//! let config = Config::init().unwrap(); //! -//! assert_eq!(config.db_host, "localhost"); -//! assert_eq!(config.db_port, Some(5432)); -//! assert_eq!(config.http_port, 8080); -//! } +//! assert_eq!(config.db_host, "localhost"); +//! assert_eq!(config.db_port, Some(5432)); +//! assert_eq!(config.http_port, 8080); //! ``` //! //! The library uses `std::str::FromStr` trait to convert environment variables into custom diff --git a/envconfig_derive/src/lib.rs b/envconfig_derive/src/lib.rs index f423c10..eb1dd6d 100644 --- a/envconfig_derive/src/lib.rs +++ b/envconfig_derive/src/lib.rs @@ -60,19 +60,46 @@ fn gen_field_assign(field: &Field) -> proc_macro2::TokenStream { let attr = fetch_envconfig_attr_from_field(field); if let Some(attr) = attr { + // if #[envconfig(...)] is there let list = fetch_list_from_attr(field, attr); - let from_value = find_item_in_list_or_panic(field, &list, "from"); + + // If nested attribute is present + let nested_value_opt = find_item_in_list(field, &list, "nested"); + if nested_value_opt.is_some() { + return gen_field_assign_for_struct_type(field); + } + let opt_default = find_item_in_list(field, &list, "default"); - let field_type = &field.ty; + let from_opt = find_item_in_list(field, &list, "from"); + let env_var = match from_opt { + Some(v) => quote! { #v }, + None => field_to_env_var(field), + }; - if to_s(field_type).starts_with("Option ") { - gen_field_assign_for_optional_type(field, from_value, opt_default) - } else { - gen_field_assign_for_non_optional_type(field, from_value, opt_default) - } + geen(field, env_var, opt_default) + } else { + // if #[envconfig(...)] is not present + let env_var = field_to_env_var(field); + geen(field, env_var, None) + } +} + +fn field_to_env_var(field: &Field) -> proc_macro2::TokenStream { + let field_name = field.clone().ident.unwrap().to_string().to_uppercase(); + quote! { #field_name } +} + +fn geen( + field: &Field, + from: proc_macro2::TokenStream, + opt_default: Option<&Lit>, +) -> proc_macro2::TokenStream { + let field_type = &field.ty; + if to_s(field_type).starts_with("Option ") { + gen_field_assign_for_optional_type(field, from, opt_default) } else { - gen_field_assign_for_struct_type(field) + gen_field_assign_for_non_optional_type(field, from, opt_default) } } @@ -90,7 +117,7 @@ fn gen_field_assign_for_struct_type(field: &Field) -> proc_macro2::TokenStream { fn gen_field_assign_for_optional_type( field: &Field, - from: &Lit, + from: proc_macro2::TokenStream, opt_default: Option<&Lit>, ) -> proc_macro2::TokenStream { let ident = &field.ident; @@ -106,7 +133,7 @@ fn gen_field_assign_for_optional_type( fn gen_field_assign_for_non_optional_type( field: &Field, - from: &Lit, + from: proc_macro2::TokenStream, opt_default: Option<&Lit>, ) -> proc_macro2::TokenStream { let ident = &field.ident; @@ -148,20 +175,6 @@ fn fetch_list_from_attr(field: &Field, attr: &Attribute) -> Punctuated( - field: &Field, - list: &'l Punctuated, - item_name: &'n str, -) -> &'l Lit { - find_item_in_list(field, list, item_name).unwrap_or_else(|| { - panic!( - "`envconfig` attribute on field `{}` must contain `{}` item", - field_name(field), - item_name - ) - }) -} - fn find_item_in_list<'l, 'n>( field: &Field, list: &'l Punctuated, diff --git a/test_suite/src/main.rs b/test_suite/src/main.rs index cb7f01f..924b579 100644 --- a/test_suite/src/main.rs +++ b/test_suite/src/main.rs @@ -4,7 +4,6 @@ use envconfig::Envconfig; #[derive(Envconfig)] pub struct Config { - #[envconfig(from = "PORT")] pub port: u16, #[envconfig(from = "HOST")] diff --git a/test_suite/tests/default_env_var.rs b/test_suite/tests/default_env_var.rs new file mode 100644 index 0000000..122ea32 --- /dev/null +++ b/test_suite/tests/default_env_var.rs @@ -0,0 +1,36 @@ +extern crate envconfig; + +use envconfig::{Envconfig, Error}; +use std::env; + +#[derive(Envconfig)] +pub struct Config { + pub db_host: String, + pub db_port: Option, +} + +fn setup() { + env::remove_var("DB_HOST"); + env::remove_var("DB_PORT"); +} + +#[test] +fn test_derives_env_variable_names_automatically() { + setup(); + + env::set_var("DB_HOST", "db.example.com"); + env::set_var("DB_PORT", "5050"); + + let config = Config::init_from_env().unwrap(); + assert_eq!(config.db_host, "db.example.com"); + assert_eq!(config.db_port, Some(5050)); +} + +#[test] +fn test_var_is_missing() { + setup(); + + let err = Config::init_from_env().err().unwrap(); + let expected_err = Error::EnvVarMissing { name: "DB_HOST" }; + assert_eq!(err, expected_err); +} diff --git a/test_suite/tests/nesting.rs b/test_suite/tests/nested.rs similarity index 93% rename from test_suite/tests/nesting.rs rename to test_suite/tests/nested.rs index 494dc90..8eb33d9 100644 --- a/test_suite/tests/nesting.rs +++ b/test_suite/tests/nested.rs @@ -14,12 +14,16 @@ pub struct DBConfig { #[derive(Envconfig)] pub struct Config { + #[envconfig(nested = true)] pub db: DBConfig, } #[derive(Envconfig)] pub struct ConfigDouble { + #[envconfig(nested = true)] pub db1: DBConfig, + + #[envconfig(nested = true)] pub db2: DBConfig, }