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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions