diff --git a/from-env-derive/Cargo.toml b/from-env-derive/Cargo.toml index 0f482c9..37cbc44 100644 --- a/from-env-derive/Cargo.toml +++ b/from-env-derive/Cargo.toml @@ -2,7 +2,7 @@ name = "init4-from-env-derive" description = "A derive macro for `init4_bin_base::FromEnv`" -version = "0.1.1" +version = "0.1.2" edition = "2021" rust-version = "1.81" authors = ["init4", "James Prestwich"] @@ -20,4 +20,4 @@ syn = { version = "2.0.100", features = ["full", "parsing"] } proc-macro = true [dev-dependencies] -init4-bin-base = "0.3" +init4-bin-base = "0.9" diff --git a/from-env-derive/src/field.rs b/from-env-derive/src/field.rs index caf2ee5..11737ba 100644 --- a/from-env-derive/src/field.rs +++ b/from-env-derive/src/field.rs @@ -114,7 +114,7 @@ impl Field { return field_name.clone(); } - let n = format!("field_{}", idx); + let n = format!("field_{idx}"); syn::parse_str::(&n) .map_err(|_| syn::Error::new(self.span, "Failed to create field name")) .unwrap() @@ -199,7 +199,7 @@ impl Field { }) } - pub(crate) fn expand_item_from_env(&self, err_ident: &Ident, idx: usize) -> TokenStream { + pub(crate) fn expand_item_from_env(&self, err_ident: &syn::Path, idx: usize) -> TokenStream { // Produces code fo the following form: // ```rust // // EITHER diff --git a/from-env-derive/src/lib.rs b/from-env-derive/src/lib.rs index e16b7fd..2cfabb3 100644 --- a/from-env-derive/src/lib.rs +++ b/from-env-derive/src/lib.rs @@ -102,15 +102,23 @@ impl Input { } } - fn error_ident(&self) -> syn::Ident { + fn error_ident(&self) -> syn::Path { + if self.is_infallible() { + return syn::parse_str("::std::convert::Infallible").unwrap(); + } + let error_name = format!("{}EnvError", self.ident); - syn::parse_str::(&error_name) + syn::parse_str::(&error_name) .map_err(|_| { syn::Error::new(self.ident.span(), "Failed to parse error ident").to_compile_error() }) .unwrap() } + fn is_infallible(&self) -> bool { + self.error_variants().is_empty() + } + fn error_variants(&self) -> Vec { self.fields .iter() @@ -152,6 +160,10 @@ impl Input { let error_variant_displays = self.error_variant_displays(); let error_variant_sources = self.expand_variant_sources(); + if error_variants.is_empty() { + return Default::default(); + } + quote! { #[doc = "Generated error type for [`FromEnv`] for"] #[doc = #struct_name_str] @@ -195,6 +207,7 @@ impl Input { fn expand_impl(&self) -> TokenStream { let env_item_info = self.env_item_info(); let struct_name = &self.ident; + let error_ident = self.error_ident(); let item_from_envs = self.item_from_envs(); @@ -226,16 +239,25 @@ impl Input { fn expand_mod(&self) -> TokenStream { // let expanded_impl = expand_impl(input); - let expanded_error = self.expand_error(); let expanded_impl = self.expand_impl(); let crate_name = &self.crate_name; - let error_ident = self.error_ident(); let mod_ident = syn::parse_str::(&format!("__from_env_impls_{}", self.ident)).unwrap(); + let expanded_error = self.expand_error(); + + let use_err = if !expanded_error.is_empty() { + let error_ident = self.error_ident(); + quote! { + pub use #mod_ident::#error_ident; + } + } else { + quote! {} + }; + quote! { - pub use #mod_ident::#error_ident; + #use_err mod #mod_ident { use super::*; use #crate_name::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar, EnvItemInfo}; diff --git a/from-env-derive/tests/macro.rs b/from-env-derive/tests/macro.rs index b200a2b..9ed4979 100644 --- a/from-env-derive/tests/macro.rs +++ b/from-env-derive/tests/macro.rs @@ -1,5 +1,16 @@ use init4_from_env_derive::FromEnv; +// Compile check for a struct with no fallible fields (and therefore no +// associated env error). +#[derive(Debug, FromEnv)] +pub struct InfallibleConfig { + #[from_env(var = "INTERNAL", desc = "An infallible string", infallible)] + pub internal: String, + + #[from_env(var = "INTERNAL_COW", desc = "An infallible Cow", infallible)] + pub internal_cow: std::borrow::Cow<'static, str>, +} + #[derive(FromEnv, Debug)] pub struct FromEnvTest { /// This is a guy named tony @@ -81,10 +92,10 @@ mod test { } fn assert_contains(vec: &Vec<&'static EnvItemInfo>, item: &EnvItemInfo) { - let item = vec.iter().find(|i| i.var == item.var).unwrap(); - assert_eq!(item.var, item.var); - assert_eq!(item.description, item.description); - assert_eq!(item.optional, item.optional); + let i = vec.iter().find(|i| i.var == item.var).unwrap(); + assert_eq!(i.var, item.var); + assert_eq!(i.description, item.description); + assert_eq!(i.optional, item.optional); } #[test]