Expand description
Environment variables for deser.
use deser::Deserialize;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
debug: bool,
server: Server,
}
#[derive(Debug, Deserialize)]
struct Server {
port: u16,
max_connections: Option<u32>,
}
// `deser_env::from_env::<Config>("APP_")` reads the environment of the
// process, `from_vars` reads the given variables
let config: Config = deser_env::from_vars("APP_", [
("APP_NAME", "shop"),
("APP_DEBUG", "yes"),
("APP_SERVER__PORT", "8080"),
("PATH", "/usr/bin"),
])
.unwrap();
assert_eq!(config.name, "shop");
assert!(config.debug);
assert_eq!(config.server.port, 8080);
assert_eq!(config.server.max_connections, None);§Data Model
The variables with names that start with a prefix (like APP_) are a
map. The prefix is removed and the rest of the name is split at the
separator (__ by default, see DeserializerConfig::set_separator) into
nested keys which are lowercased (see Case):
| variables | deser |
|---|---|
APP_PORT=80 | {"port": "80"} |
APP_MAX_CONNECTIONS=5 | {"max_connections": "5"} |
APP_SERVER__PORT=80 | {"server": {"port": "80"}} |
APP_HOSTS__0=a APP_HOSTS__1=b | {"hosts": ["a", "b"]} |
APP_HOSTS__1=a APP_HOSTS__3=b | {"hosts": {"1": "a", "3": "b"}} |
APP_FEATURES__NEW_UI=on | {"features": {"new_ui": "on"}} |
A single _ separates words in names, which is why nested keys are
separated with two. Indexes that start at 0 and have no gaps are
sequences, other indexes are map keys. Names that start or end with the
separator or contain it twice in a row are taken as they are. A name
that has a value and nested names (APP_DB=x and APP_DB__POOL=4) is an
error. The order of the environment is arbitrary, the variables are
sorted by name.
Everything in the environment is text, the type of a value is only known
to the type it’s deserialized into. Keys and values are therefore passed
on as lexical atoms which are parsed by the
types they are delivered to: numbers parse them, strings take them as
they are. Booleans accept true, yes, on and 1 and false, no,
off and 0. Lexical atoms are retained when values are buffered, so
flattened structs and internally tagged and untagged enums work as well.
§Empty Values
A variable that is set to the empty string is the empty value, like
?page= in a query string: it’s None for optionals of types that do
not accept it (APP_PORT= is None for an Option<u16>) and
Some("") for an Option<String>. Variables that switch something on
by being set (like APP_VERBOSE=) use the
Flag adapter which treats the empty
value as true:
use deser::adapters::Flag;
#[derive(deser::Deserialize)]
struct Options {
#[deser(as = Flag)]
verbose: bool,
port: Option<u16>,
}
let options: Options =
deser_env::from_vars("APP_", [("APP_VERBOSE", ""), ("APP_PORT", "")])
.unwrap();
assert!(options.verbose);
assert_eq!(options.port, None);§Lists
Sequences can be given with indexes (APP_HOSTS__0) without changes to
the types. A single variable (APP_HOSTS=a) is a list of one value and
a list without variables is empty (the maps of the environment are
multimaps, like query
strings). Lists in a single variable (APP_HOSTS=a,b,c) use the
Separated adapter, with
TrimWhitespace to allow spaces
(a, b, c). Both work with all formats, a configuration file can still
give an array:
use deser::adapters::{Separated, TrimWhitespace};
#[derive(deser::Deserialize)]
struct Config {
#[deser(as = Separated<',', TrimWhitespace>)]
hosts: Vec<String>,
ports: Vec<u16>,
tags: Vec<String>,
}
let config: Config = deser_env::from_vars("APP_", [
("APP_HOSTS", "a, b"),
("APP_PORTS__0", "80"),
("APP_PORTS__1", "443"),
])
.unwrap();
assert_eq!(config.hosts, ["a", "b"]);
assert_eq!(config.ports, [80, 443]);
assert!(config.tags.is_empty());§Errors
Errors carry the name of the variable they refer to (see EnvVar).
This is also the case for unknown fields that are collected as warnings
(see UnknownFields) and for values
which are buffered:
use deser_env::EnvVar;
#[derive(Debug, deser::Deserialize)]
struct Config {
port: u16,
}
let vars = [("APP_PORT", "http")];
let err = deser_env::from_vars::<Config, _, _, _>("APP_", vars)
.unwrap_err();
assert_eq!(err.attachment::<EnvVar>().unwrap().name(), "APP_PORT");
assert_eq!(
err.to_string(),
"InvalidValue: invalid value \"http\", expected u16 \
(environment variable APP_PORT)"
);§Layering
Environment variables typically override a configuration file. With
update the variables that are
set are applied to an existing value, nested structs are merged:
use deser::de::Deserializer as _;
#[derive(Debug, deser::Deserialize)]
struct Config {
server: Server,
}
#[derive(Debug, deser::Deserialize)]
struct Server {
host: String,
port: u16,
}
let mut config = Config {
server: Server { host: "localhost".into(), port: 80 },
};
deser_env::Deserializer::from_vars("APP_", [("APP_SERVER__PORT", "8080")])
.update(&mut config)
.unwrap();
assert_eq!(config.server.host, "localhost");
assert_eq!(config.server.port, 8080);§Platforms
On Unix values which are not valid unicode are passed on as
bytes (which Vec<u8> and PathBuf
accept), on other platforms they are an error. Names that are not valid
unicode are skipped unless they start with the prefix. On Windows names
are not case sensitive, the prefix is matched ignoring ASCII case.
Setting environment variables of the running process is unsafe (see
std::env::set_var), from_vars is a better fit for tests.
§Serialization
Values are serialized into name-value pairs (see to_vars and
SerializerConfig), for instance to pass a configuration to a child
process with Command::envs.
§Features
speedups(enabled by default): has no effect yet, it exists so that all formats have it.
Structs§
- Deserializer
- Deserializes environment variables.
- Deserializer
Config - Configures how environment variables are deserialized.
- Deserializer
Config Builder - Builds a
DeserializerConfig. - EnvVar
- The environment variable an error refers to.
- Serializer
Config - Configures how values are serialized into environment variables.
- Serializer
Config Builder - Builds a
SerializerConfig.
Enums§
- Case
- How the case of names maps onto keys.