mirror of
https://github.com/OMGeeky/confique.git
synced 2026-01-08 20:47:04 +01:00
Add parse_env attribute to confique::Config macro
Since there is no established format for representing collections within env variables, to enable their support, the feature with a custom parser attribute added. Add helpers for parsing env variables into collections Improve rustdoc for `env_utils` functions Improve `parse_env` example
This commit is contained in:
103
src/env.rs
103
src/env.rs
@@ -128,7 +128,6 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -166,3 +165,105 @@ mod tests {
|
||||
assert_eq!(de("-123.456"), Ok(-123.456f64));
|
||||
}
|
||||
}
|
||||
|
||||
/// This module contains helper methods to simplify configuration via environment variables
|
||||
pub mod env_utils {
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Helper function to parse any type that implements [`std::str::FromStr`]
|
||||
/// into a collection that implements [`std::iter::FromIterator`], spliting original string by
|
||||
/// const char separator.
|
||||
///
|
||||
/// # To Vec
|
||||
/// ```
|
||||
/// use crate::confique::Config;
|
||||
/// #[derive(Debug, confique::Config)]
|
||||
/// struct Conf {
|
||||
/// #[config(
|
||||
/// env = "PORTS",
|
||||
/// parse_env = confique::env_utils::to_collection_by_char_separator::<',', _, _>
|
||||
/// )]
|
||||
/// ports: Vec<u16>
|
||||
/// }
|
||||
///
|
||||
/// std::env::set_var("PORTS", "8080,8000,8888");
|
||||
/// println!("{:?}", Conf::builder().env().load().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # To HashSet
|
||||
/// ```
|
||||
/// use crate::confique::Config;
|
||||
/// #[derive(Debug, confique::Config)]
|
||||
/// struct Conf {
|
||||
/// #[config(
|
||||
/// env = "PATHS",
|
||||
/// parse_env = confique::env_utils::to_collection_by_char_separator::<';', _, _>
|
||||
/// )]
|
||||
/// paths: std::collections::HashSet<std::path::PathBuf>,
|
||||
/// }
|
||||
///
|
||||
/// std::env::set_var("PATHS", "/bin;/user/bin;/home/user/.cargo/bin");
|
||||
/// println!("{:?}", Conf::builder().env().load().unwrap())
|
||||
/// ```
|
||||
pub fn to_collection_by_char_separator<
|
||||
const SEPARATOR: char,
|
||||
T: FromStr,
|
||||
C: FromIterator<Result<T, <T as FromStr>::Err>>,
|
||||
>(
|
||||
input: &str,
|
||||
) -> C {
|
||||
input.split(SEPARATOR.to_owned()).map(T::from_str).collect()
|
||||
}
|
||||
|
||||
macro_rules! specify_fn_wrapper {
|
||||
($symbol_name:ident, $symbol:tt) => {
|
||||
::paste::paste! {
|
||||
/// Helper function to parse any type that implements [`std::str::FromStr`]
|
||||
/// into a collection that implements [`std::iter::FromIterator`], spliting original string by
|
||||
#[doc = stringify!($symbol_name)]
|
||||
/// .
|
||||
///
|
||||
/// # To Vec
|
||||
/// ```
|
||||
/// use crate::confique::Config;
|
||||
/// #[derive(Debug, confique::Config)]
|
||||
/// struct Conf {
|
||||
/// #[config(
|
||||
/// env = "PORTS",
|
||||
#[doc = concat!(" parse_env = confique::env_utils::", stringify!([<to_collection_by_ $symbol_name>],))]
|
||||
/// )]
|
||||
/// ports: Vec<u16>
|
||||
/// }
|
||||
///
|
||||
#[doc = concat!("std::env::set_var(\"PORTS\", \"8080", $symbol, "8000", $symbol, "8888", "\");")]
|
||||
/// println!("{:#?}", Conf::builder().env().load().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # To HashSet
|
||||
/// ```
|
||||
/// use crate::confique::Config;
|
||||
/// #[derive(Debug, confique::Config)]
|
||||
/// struct Conf {
|
||||
/// #[config(
|
||||
/// env = "PATHS",
|
||||
#[doc = concat!(" parse_env = confique::env_utils::", stringify!([<to_collection_by_ $symbol_name>],))]
|
||||
/// )]
|
||||
/// paths: std::collections::HashSet<std::path::PathBuf>,
|
||||
/// }
|
||||
///
|
||||
#[doc = concat!("std::env::set_var(\"PATHS\", \"/bin", $symbol, "/user/bin", $symbol, "/home/user/.cargo/bin", "\");")]
|
||||
/// println!("{:#?}", Conf::builder().env().load().unwrap())
|
||||
/// ```
|
||||
pub fn [<to_collection_by_ $symbol_name>]<T: FromStr, C: FromIterator<Result<T, <T as FromStr>::Err>>>(
|
||||
input: &str,
|
||||
) -> C {
|
||||
to_collection_by_char_separator::<$symbol, _, _>(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specify_fn_wrapper!(comma, ',');
|
||||
specify_fn_wrapper!(semicolon, ';');
|
||||
specify_fn_wrapper!(space, ' ');
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
//! intended to be used directly. None of this is covered by semver! Do not use
|
||||
//! any of this directly.
|
||||
|
||||
use crate::{error::ErrorInner, Error};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::{error::ErrorInner, Error};
|
||||
|
||||
pub fn deserialize_default<I, O>(src: I) -> Result<O, serde::de::value::Error>
|
||||
where
|
||||
@@ -41,10 +42,29 @@ pub fn from_env<'de, T: serde::Deserialize<'de>>(
|
||||
key: &str,
|
||||
field: &str,
|
||||
) -> Result<Option<T>, Error> {
|
||||
from_env_with(key, field, |de| T::deserialize(de))
|
||||
deserialize_from_env_with(key, field, |de| T::deserialize(de))
|
||||
}
|
||||
|
||||
pub fn from_env_with<T>(
|
||||
pub fn parse_from_env_with<T, E: Debug>(
|
||||
key: &str,
|
||||
field: &str,
|
||||
parse: fn(&str) -> Result<T, E>,
|
||||
) -> Result<Option<T>, Error> {
|
||||
from_env::<String>(key, field)?
|
||||
.as_deref()
|
||||
.map(parse)
|
||||
.transpose()
|
||||
.map_err(|err| {
|
||||
ErrorInner::EnvDeserialization {
|
||||
field: field.to_owned(),
|
||||
key: key.to_owned(),
|
||||
msg: format!("Error while parse: {:?}", err),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize_from_env_with<T>(
|
||||
key: &str,
|
||||
field: &str,
|
||||
deserialize: fn(crate::env::Deserializer) -> Result<T, crate::env::DeError>,
|
||||
|
||||
@@ -176,6 +176,7 @@ pub mod internal;
|
||||
|
||||
mod builder;
|
||||
mod env;
|
||||
pub use env::env_utils;
|
||||
mod error;
|
||||
pub mod meta;
|
||||
|
||||
@@ -322,6 +323,9 @@ pub use crate::{
|
||||
///
|
||||
/// [serde-deser]: https://serde.rs/field-attrs.html#deserialize_with
|
||||
///
|
||||
/// - **`#[config(from_env = path::to::function, env = "KEY")]`**: like
|
||||
/// deserialize_with` attribute, but only for fields from an environment variable.
|
||||
/// Can only be present if the `env` attribute is present
|
||||
///
|
||||
/// ## Special types for leaf fields
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user