Loading lists or maps from env vars
Closed this issue · 4 comments
It could be useful to load lists from environment vars. But then the big question is: what format? Comma separated? There are too many options (JSON or simply separated by ,; :
) and none can be used as a solution for everything. So it needs to be configurable for the library user.
I suppose it is already possible now by using deserialize_with
, but that's not particularly convenient.
Why not just require the delimiter
property in the field attribute if env is given for a collection property?
#[derive(Debug, Config)]
pub struct Configuration {
#[config(env = "PORT", env_delimiter = ";")]
pub ports: Vec<u16>,
}
That's a possibility, but maybe not flexible enough. Some people might want to have lists like PORTS="[80,443]"
. And what about maps? I think it would be better to let users provide their own parsing function. Maybe with some built-in functions for widely used syntax.
#[config(env = "PORTS", parse_env = ...)]
Although this functionality already exists, this code will work:
use confique::Config;
#[derive(Debug, Config)]
pub struct Configuration {
#[config(env = "PORTS", deserialize_with = deserialize_vec)]
pub ports: Vec<u16>,
}
pub(crate) fn deserialize_vec<'de, D>(deserializer: D) -> Result<Vec<u16>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, Deserialize};
String::deserialize(deserializer)?
.split(',')
.map(|port| {
port.parse::<u16>().map_err(|err| {
de::Error::custom(format!(
"Unexpected error while parse {} as string with port: {:?}",
port, err
))
})
})
.collect::<Result<_, _>>()
}
fn main() {
std::env::set_var("PORTS", "10,20");
let config = Configuration::builder().env().load().unwrap();
assert_eq!(&config.ports, &[10, 20]);
}
It turns out that the whole feature is precisely to add parse_env
, which will only work for the env format and does not make changes to the parsing for other formats.
It turns out that the whole feature is precisely to add
parse_env
, which will only work for the env format and does not make changes to the parsing for other formats.
Yep, I think that's the main benefit of a parse_env
over the current deserialize_with
. And well, something like fn(&str) -> Result<T, _>
is a bit easier to implement than the deserialize_with
. Certainly sounds like a plan!