Add Partial::from_env and implement it in derive macro

This commit is contained in:
Lukas Kalbertodt
2021-07-27 01:12:57 +02:00
parent 3ac922ca2f
commit aa5eb06f49
4 changed files with 56 additions and 1 deletions

View File

@@ -120,6 +120,19 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
}
});
let from_env_fields = input.fields.iter().map(|f| {
match &f.kind {
FieldKind::Leaf { env: Some(key), .. } => {
let field = format!("{}::{}", input.name, f.name);
quote! {
confique::internal::from_env(#key, #field)?
}
}
FieldKind::Leaf { .. } => quote! { None },
FieldKind::Nested { .. } => quote! { confique::Partial::from_env()? },
}
});
let fallbacks= input.fields.iter().map(|f| {
let name = &f.name;
if f.is_leaf() {
@@ -174,6 +187,12 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
}
}
fn from_env() -> Result<Self, confique::Error> {
Ok(Self {
#( #field_names: #from_env_fields, )*
})
}
fn with_fallback(self, fallback: Self) -> Self {
Self {
#( #field_names: #fallbacks, )*

View File

@@ -28,6 +28,14 @@ pub(crate) enum ErrorInner {
err: Box<dyn std::error::Error + Send + Sync>,
},
/// When deserialization via `env` fails. The string is what is passed to
/// `serde::de::Error::custom`.
EnvDeserialization {
field: String,
key: String,
msg: String,
},
/// Returned by the [`Source`] impls for `Path` and `PathBuf` if the file
/// extension is not supported by confique or if the corresponding Cargo
/// feature of confique was not enabled.
@@ -53,6 +61,7 @@ impl std::error::Error for Error {
ErrorInner::Io { err, .. } => Some(err),
ErrorInner::Deserialization { err, .. } => Some(&**err),
ErrorInner::MissingValue(_)
| ErrorInner::EnvDeserialization { .. }
| ErrorInner::UnsupportedFileFormat { .. }
| ErrorInner::MissingFileExtension { .. }
| ErrorInner::MissingRequiredFile { .. } => None,
@@ -81,6 +90,14 @@ impl fmt::Display for Error {
ErrorInner::Deserialization { source: None, .. } => {
std::write!(f, "failed to deserialize configuration")
}
ErrorInner::EnvDeserialization { field, key, msg } => {
std::write!(f,
"failed to deserialize value `{}` from environment variable `{}`: {}",
field,
key,
msg,
)
}
ErrorInner::UnsupportedFileFormat { path } => {
std::write!(f,
"unknown configuration file format/extension: '{}'",

View File

@@ -1,4 +1,4 @@
//! These functions are used by the code generated by the macro, but is not
//! These functions are used by the code generated by the macro, but are not
//! intended to be used directly. None of this is covered by semver! Do not use
//! any of this directly.
@@ -24,3 +24,13 @@ pub fn prepend_missing_value_error(e: Error, prefix: &str) -> Error {
e => e.into(),
}
}
pub fn from_env<'de, T: serde::Deserialize<'de>>(key: &str, field: &str) -> Result<T, Error> {
crate::env::deserialize(std::env::var(key).ok()).map_err(|e| {
ErrorInner::EnvDeserialization {
key: key.into(),
field: field.into(),
msg: e.0,
}.into()
})
}

View File

@@ -105,6 +105,15 @@ pub trait Partial: for<'de> Deserialize<'de> {
/// values/fields set to `None`/being empty.
fn default_values() -> Self;
/// Loads values from environment variables. This is only relevant for
/// fields annotated with `#[config(env = "...")]`: all fields not
/// annotated `env` will be `None`.
///
/// If the env variable corresponding to a field is not set, that field is
/// `None`. If it is set but it failed to deserialize into the target type,
/// an error is returned.
fn from_env() -> Result<Self, Error>;
/// Combines two partial configuration objects. `self` has a higher
/// priority; missing values in `self` are filled with values in `fallback`,
/// if they exist. The semantics of this method is basically like in