Skip to content

Underscore field nesting for single _ separators #704

@aleris

Description

@aleris

Nested structs for configuration are used pretty often.
When overridden with environment variables this works well if the field names do not have _ in names.
For example with:

#[derive(Deserialize, Debug)]
struct TestConfig {
    single: String,
    plain: SimpleInner,
}

#[derive(Deserialize, Debug)]
struct SimpleInner {
    val: String,
}

Then we can override with:

PREFIX_SINGLE=test
PREFIX_PLAIN_VAL=simple

However, Rust default naming convention for fields is with _, so any fields which has multiple parts like timeout_millis or host_name becomes an issue.

The standard expectation for this is that single underscore will still work, like for example with:
PREFIX_INNER_TIMEOUT_MILLIS, however this does not work because it matches the internal path inner.timeout.millis instead of inner.timeout_millis.

Currently is possible to workaround this issue by setting for example a double underscore __ as separator. For:

#[derive(Deserialize, Debug)]
    struct TestConfig {
    single: String,
    plain: SimpleInner,
    value_with_multipart_name: String,
    inner_config: ComplexInner,
}

#[derive(Deserialize, Debug)]
struct SimpleInner {
    val: String,
}

We can use it with:

let environment = Environment::default()
                .prefix("PREFIX")
                .separator("__");

And then we can override from env vars with:

PREFIX__SINGLE=test
PREFIX__PLAIN__VAL=simple
PREFIX__VALUE_WITH_MULTIPART_NAME=value1
PREFIX__INNER_CONFIG__ANOTHER_MULTIPART_NAME=value2

However, using double underscores also has some issues, is unexpected and is error prone (is easy to mistake _ vs __).

There is a related issue which discusses a bit the problem from the documentation and edge cases perspective:
#596.
There are there some proposals for solving the issue:

  1. Hand-written env names
  2. facet crate or schemars crate for generating env name from config structure.

Another alternative is to:
3. try to match the combined segments from the nested name with the actual path. There is a draft PR with the implementation here: #703

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions