config-rs
config-rs copied to clipboard
Cannot get config-rs to read env variables
Getting the following error when trying to get an environmental variable from config-rs:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: configuration property "DEV_SERVER_ADDRESS" not found', src/app/config.rs:14:27 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
use config::Config;
use lazy_static;
lazy_static! {
#[derive(Debug)]
pub static ref CONFIG: Config = Config::builder()
.add_source(config::Environment::with_prefix("APP_NAME").separator("_"))
.add_source(config::File::with_name("AppName"))
.build()
.unwrap();
}
pub fn get<'a, T: serde::Deserialize<'a>>(key: &str) -> T {
CONFIG.get::<T>(&key).unwrap()
}
ENV variable:
export APP_NAME_DEV_SERVER_ADDRESS="testtt dev addr"
Trying to get the variable in main:
#[actix_web::main]
async fn main() -> result::Result<(), io::Error> {
let dev_server_address: String = app::config::get("DEV_SERVER_ADDRESS");
println!("DEV_SERVER_ADDRESS: {}", dev_server_address);
...
What am I doing wrong?
Hi!
Thanks for reporting this! Please have a look at the code in #392 - maybe I am missing something, because that example works for me (assuming you use config-rs from master).
The only difference I spot is that you're using the actix_web::main macro for your main function, but I doubt that this makes a difference...
FWIW, I also cannot get this working with 0.13.2 and nested structs in my config.
When I use .add_source(config::Environment::with_prefix("TEST")) then I can successfully use environment variables like TEST_MYSTRUCT.MYFIELD
However, when I use .add_source(config::Environment::with_prefix("TEST").separator("_")), no environment variables seem to work at all:
TEST_MYSTRUCT.MYFIELD-- no longer works, which is expected since the separator should be_TEST_MYSTRUCT_MYFIELD-- does not work, which is NOT expected
Something about separator(...) does not seem to behave as I would expect
Hi!
Thanks for reporting this! Please have a look at the code in #392 - maybe I am missing something, because that example works for me (assuming you use config-rs from master).
The only difference I spot is that you're using the
actix_web::mainmacro for yourmainfunction, but I doubt that this makes a difference...
Your example is exactly what I have, but it does not work for me. As for actix_web::main I am trying to load up env variables and then start an Actix web server in main.
Any chance you can share the original code? Maybe a link to a git repository? So I can investigate a bit better?
@matthiasbeyer Sure thing, sent an invitation for: https://github.com/brsnik/actix-config-rs
Okay so, it works in lowercase app::config::get("dev_server_address") and without the separator separator("_") as @ianling suggested.
meh.
All this is really inconvenient. I hope I can make this better in the future with the rewrite I am planning.
Here is the problem:
https://github.com/mehcode/config-rs/blob/master/src/env.rs#L222
// If separator is given replace with `.`
if !separator.is_empty() {
key = key.replace(separator, ".");
}
If I have a struct in my config with a snake-case field (e.g. person.first_name) and I try to set it using the environment variable PERSON_FIRST_NAME, this block transforms the key into person.first.name instead of person.first_name
The (bad) solution that immediately springs to mind is to only replace the first occurrence of the separator with a dot using replacen(..., 1) instead of replace(...), but then that leaves structs whose names contain snake-case broken, as well as deeply nested structs:
- using the env variable
PERSON_STRUCT_NAMEto set the struct fieldperson_struct.namewon't work because this solution would set the key toperson.struct_name - using the env variable
PERSON_NAME_FIRST_NAMEto set the nested struct fieldperson.name.first_namewon't work because this solution would set the key toperson.name_first_name
Hard saying what a good solution would be for this, as I can't think of a way for this crate to know for sure if the environment variable PERSON_NAME_FIRST_NAME is supposed to mean person.name.first_name or person_name.first_name or even just person_name_first_name....
I honestly fail to see the need for the separator whatsoever. Keys like person.first.name and person.first_name are incredibly confusing.
Should be snake case all the way.
This crate allows you to parse a config into an actual struct using .try_deserialize(), rather than retrieving keys using strings:
https://github.com/mehcode/config-rs/blob/c8ee9f1335fb093cda3debabe15ad0f7a3ab9071/examples/env-list/main.rs
My use case is ultimately setting values in my config struct using environment variables, but this gets messy in the cases I mentioned above due to some ambiguous combinations of dots and underscores.
This is something I'm running into now myself and I'm having to resort to using #[serde(alias = "oneWord")] to allow fields like session_secret_key to be seen as sessionkey. Pretty undesirable, but it works.
In this vein, though probably not possible, if we could get config to know what kind of env to fill a field with in maybe some sort of more custom deserialization, that'd could allow people to specify the full env string a field could match to.
(Originally published at: https://jacky.wtf/2022/11/Uqih)
In this vein, though probably not possible, if we could get config to know what kind of env to fill a field with in maybe some sort of more custom deserialization, that'd could allow people to specify the full env string a field could match to.
That is how the popular Go module envconfig works: https://github.com/kelseyhightower/envconfig#struct-tag-support
That said, Go's syntax works a little better for this, in my opinion, since it's all on one line, vs having an additional #[serde(...)] line above each field in Rust.
What if for person.first_name you use a a dunderscore __ as the separator? Then you could write PERSON__FIRST_NAME and the replace would change it to PERSON.FIRST_NAME?
It will work as expected if used like this:
.add_source(Environment::with_prefix("app").prefix_separator("__").separator("__"))
then
APP__BRAINTREE__MERCHANT_ID will be correctly parsed to Braintree.merchant_id with snake_case setting name.
Probably an example could be added to cover this case.
@nahuakang Great idea. A little weird at first, but it is a perfectly good solution to the problem @ivan-mudrak and @ianling mention above with snake_case struct field names. I'll take that over the serde field name aliasing approach.
It would be good to surface this in the docs, leaving a comment here so I remember to come back later and do this.
I just spent half an hour running a debugger getting to the bottom of this, only to realize that fundamentally _ as the separator while having _ in the variable names will never work, based on the approach in the code. I assumed that it was doing something like 'for each variable in the config schema, check for an env-var', which would be predictable and scale with the size of the schema. But instead it's doing "check each envvar and if it matches the rules, try to insert it to the global config map", without any schema-enriched info, which doesn't work because _s get translated to .s. The approach taken also doesn't seem to surface a warning anywhere when a close-but-not-quite envvar was set but is having no impact.
It may also be good to default the separator to __, since _ will be so common in struct field names.