libcorn passes all environment variables to serde as strings, regardless of the underlying type · Issue #49 · corn-config/corn · GitHub
Skip to content

libcorn passes all environment variables to serde as strings, regardless of the underlying type #49

Open

Description

@tarka

Having native environment variable injection is a powerful feature as it allows 12-factor type configuration, however it doesn't play well with non-string types (AFAICS; happy to be proven wrong).

e.g. If you have the following config definitions (based on a real use-case):

#[derive(Deserialize)]
pub struct Insecure {
    pub port: u16,
    pub redirect: bool,
}

#[derive(Deserialize)]
pub struct TlsConfig {
    pub port: u16,
}

#[derive(Deserialize)]
pub struct Config {
    pub insecure: Insecure,
    pub tls: TlsConfig,
}

And this config file:

let {
    $env_INSECURE_PORT = 80
    $env_INSECURE_REDIRECT = true
    $env_TLS_PORT = 443
} in {
    insecure = {
        port = $env_INSECURE_PORT
        redirect = $env_INSECURE_REDIRECT
    }
    tls = {
        port = $env_TLS_PORT
    }
}

If you deserialise with no environment variables set then it works fine. However setting any of the env-vars will result in DeserializationError("Expected integer (u16), found 'String(\"8080\")'" (or DeserializationError("Expected boolean, found 'String(\"false\")'")).

I'm not sure if there's any simple workaround for this? It looks like the only way is to make any variables that may be provided from the environment into strings an have serde parse them into the appropriate type (with serde-with or similar). However the user could potentially supply any variable, so everything would end up being a string.

Activity

  1. tarka commented on Dec 22, 2025

    @tarka
    Author

    There's a full test-case here if it helps: https://github.com/tarka/corn-test/blob/main/src/lib.rs

  2. JakeStanger commented on Dec 22, 2025

    @JakeStanger
    Collaborator

    Interesting, this is definitely an oversight.

    In my head env vars would always be strings because that's how the OS treats them, but the specification doesn't actually state that (and even gives an integer-based example) and this is definitely a valid usecase.

    Currently you will have to treat all env vars as strings and work around that the best you can, either with serde or by parsing in code. I am definitely open to amending that though.

  3. tarka commented on Dec 23, 2025

    @tarka
    Author

    One quick workaround for this would be to specify that non-string $env inputs should have a default. This would allow you to use the inputs lookup to retrieve the expected type and attempt a coercion. This would only really work with primitive types though (int, float, bool). But that would probably cover most normal cases. I've hacked together a quick proof-of-concept here and it works for the example above.

    Obviously it would be much better to retrieve the type from the underlying deserialisation target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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