diff --git a/README.md b/README.md index 1f91ab3..a3e1b9f 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ your crate: extern crate dotenv_codegen; ``` -Then, in your crate: +Then, in your crate you may retrieve a value like so: ```rust fn main() { @@ -126,4 +126,25 @@ fn main() { } ``` +You may also panic with a supplied error message if the variable does +not exist: + +```rust +fn main() { + dotenv!("A_MISSING_VARIABLE", "This is an error message!"); +} +``` + +Or you can supply a default value if the variable does not exist: + +```rust +fn main() { + let meaning_of_life: &str = dotenv_or_default!( + "A_MISSING_VARIABLE", + "42" + ); + println!("{}", meaning_of_life); +} +``` + [dotenv]: https://github.com/bkeepers/dotenv diff --git a/dotenv_codegen/Cargo.toml b/dotenv_codegen/Cargo.toml index 5227f0d..943a2a2 100644 --- a/dotenv_codegen/Cargo.toml +++ b/dotenv_codegen/Cargo.toml @@ -20,3 +20,6 @@ edition = "2018" [dependencies] dotenv_codegen_implementation = { version = "0.15", path = "../dotenv_codegen_implementation" } proc-macro-hack = "0.5" + +[dev-dependencies] +trybuild = "1.0.32" diff --git a/dotenv_codegen/src/lib.rs b/dotenv_codegen/src/lib.rs index c7588d4..0538e5f 100644 --- a/dotenv_codegen/src/lib.rs +++ b/dotenv_codegen/src/lib.rs @@ -1,4 +1,4 @@ use proc_macro_hack::proc_macro_hack; #[proc_macro_hack] -pub use dotenv_codegen_implementation::dotenv; +pub use dotenv_codegen_implementation::{dotenv, dotenv_or_default}; diff --git a/dotenv_codegen/tests/basic_dotenv_macro.rs b/dotenv_codegen/tests/basic_dotenv_macro.rs index 1b4c1c0..b9c2a50 100644 --- a/dotenv_codegen/tests/basic_dotenv_macro.rs +++ b/dotenv_codegen/tests/basic_dotenv_macro.rs @@ -17,3 +17,15 @@ fn two_argument_form_works() { "'quotes within quotes'" ); } + +#[test] +fn dotenv_or_default_works() { + let default_value: &str = dotenv_or_default!("CODEGEN_TEST_NONEXISTING_VARIABLE", "hello!"); + assert_eq!(default_value, "hello!"); +} + +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/dotenv_codegen/tests/ui/dotenv_no_args.rs b/dotenv_codegen/tests/ui/dotenv_no_args.rs new file mode 100644 index 0000000..4fac807 --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_no_args.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate dotenv_codegen; + +pub fn main() { + dotenv!(); +} diff --git a/dotenv_codegen/tests/ui/dotenv_no_args.stderr b/dotenv_codegen/tests/ui/dotenv_no_args.stderr new file mode 100644 index 0000000..56419ca --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_no_args.stderr @@ -0,0 +1,16 @@ +error: proc-macro derive panicked + --> $DIR/dotenv_no_args.rs:5:5 + | +5 | dotenv!(); + | ^^^^^^^^^^ + | + = help: message: expected 1 or 2 arguments, found none + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `proc_macro_call` in this scope + --> $DIR/dotenv_no_args.rs:5:5 + | +5 | dotenv!(); + | ^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/dotenv_codegen/tests/ui/dotenv_or_default_no_args.rs b/dotenv_codegen/tests/ui/dotenv_or_default_no_args.rs new file mode 100644 index 0000000..287c071 --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_or_default_no_args.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate dotenv_codegen; + +pub fn main() { + dotenv_or_default!(); +} diff --git a/dotenv_codegen/tests/ui/dotenv_or_default_no_args.stderr b/dotenv_codegen/tests/ui/dotenv_or_default_no_args.stderr new file mode 100644 index 0000000..76ae3da --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_or_default_no_args.stderr @@ -0,0 +1,16 @@ +error: proc-macro derive panicked + --> $DIR/dotenv_or_default_no_args.rs:5:5 + | +5 | dotenv_or_default!(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: expected 1 or 2 arguments, found none + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `proc_macro_call` in this scope + --> $DIR/dotenv_or_default_no_args.rs:5:5 + | +5 | dotenv_or_default!(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/dotenv_codegen/tests/ui/dotenv_or_default_three_args.rs b/dotenv_codegen/tests/ui/dotenv_or_default_three_args.rs new file mode 100644 index 0000000..05dee0c --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_or_default_three_args.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate dotenv_codegen; + +pub fn main() { + dotenv_or_default!("a", "b", "c"); +} diff --git a/dotenv_codegen/tests/ui/dotenv_or_default_three_args.stderr b/dotenv_codegen/tests/ui/dotenv_or_default_three_args.stderr new file mode 100644 index 0000000..d641680 --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_or_default_three_args.stderr @@ -0,0 +1,16 @@ +error: proc-macro derive panicked + --> $DIR/dotenv_or_default_three_args.rs:5:5 + | +5 | dotenv_or_default!("a", "b", "c"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: expected 1 or 2 arguments, found 3 or more + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `proc_macro_call` in this scope + --> $DIR/dotenv_or_default_three_args.rs:5:5 + | +5 | dotenv_or_default!("a", "b", "c"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/dotenv_codegen/tests/ui/dotenv_three_args.rs b/dotenv_codegen/tests/ui/dotenv_three_args.rs new file mode 100644 index 0000000..04d584a --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_three_args.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate dotenv_codegen; + +pub fn main() { + dotenv!("a", "b", "c"); +} diff --git a/dotenv_codegen/tests/ui/dotenv_three_args.stderr b/dotenv_codegen/tests/ui/dotenv_three_args.stderr new file mode 100644 index 0000000..fdcf015 --- /dev/null +++ b/dotenv_codegen/tests/ui/dotenv_three_args.stderr @@ -0,0 +1,16 @@ +error: proc-macro derive panicked + --> $DIR/dotenv_three_args.rs:5:5 + | +5 | dotenv!("a", "b", "c"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: expected 1 or 2 arguments, found 3 or more + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot find macro `proc_macro_call` in this scope + --> $DIR/dotenv_three_args.rs:5:5 + | +5 | dotenv!("a", "b", "c"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/dotenv_codegen_implementation/src/lib.rs b/dotenv_codegen_implementation/src/lib.rs index 50a6cc2..5c17823 100644 --- a/dotenv_codegen_implementation/src/lib.rs +++ b/dotenv_codegen_implementation/src/lib.rs @@ -12,14 +12,56 @@ use syn::Token; #[proc_macro_hack] pub fn dotenv(input: TokenStream) -> TokenStream { if let Err(err) = dotenv::dotenv() { - panic!("Error loading .env file: {}", err); + let err_msg = format!("Error loading .env file: {}", err); + return quote! { + compile_error!(#err_msg); + } + .into(); } // Either everything was fine, or we didn't find an .env file (which we ignore) - expand_env(input) + let (var_name, second_value) = expand_env(input); + + let err_msg = match second_value { + Some(e) => e, + None => format!("environment variable `{}` not defined", var_name), + }; + + match env::var(var_name) { + Ok(val) => quote!(#val).into(), + Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => panic!("{}", err_msg), + } +} + +#[proc_macro_hack] +pub fn dotenv_or_default(input: TokenStream) -> TokenStream { + if let Err(err) = dotenv::dotenv() { + let err_msg = format!("Error loading .env file: {}", err); + return quote! { + compile_error!(#err_msg); + } + .into(); + } + + // Either everything was fine, or we didn't find an .env file (which we ignore) + let (var_name, second_value) = expand_env(input); + + match second_value { + Some(default) => match env::var(var_name) { + Ok(val) => quote!(#val).into(), + Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => quote!(#default).into(), + }, + None => { + let err_msg = format!("Missing default value for: {}", var_name); + (quote! { + compile_error!(#err_msg) + }) + .into() + } + } } -fn expand_env(input_raw: TokenStream) -> TokenStream { +fn expand_env(input_raw: TokenStream) -> (String, Option) { let args = >::parse_terminated .parse(input_raw) .expect("expected macro to be called with a comma-separated list of string literals"); @@ -31,17 +73,14 @@ fn expand_env(input_raw: TokenStream) -> TokenStream { None => panic!("expected 1 or 2 arguments, found none"), }; - let err_msg = match iter.next() { - Some(lit) => lit.value(), - None => format!("environment variable `{}` not defined", var_name), + let second_value = match iter.next() { + Some(lit) => Some(lit.value()), + None => None, }; if iter.next().is_some() { panic!("expected 1 or 2 arguments, found 3 or more"); } - match env::var(var_name) { - Ok(val) => quote!(#val).into(), - Err(VarError::NotPresent) | Err(VarError::NotUnicode(_)) => panic!("{}", err_msg), - } + (var_name, second_value) }