envbind 0.1.0

Typed environment binding primitives for Rust services.
Documentation
  • Coverage
  • 100%
    92 out of 92 items documented1 out of 52 items with examples
  • Size
  • Source code size: 141.45 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.4 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 9s Average build duration of successful builds.
  • all releases: 9s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • OneTesseractInMultiverse

Envbind-rs

Typed environment binding primitives for Rust services.

Envbind is an open source Rust library for typed configuration. It reads environment-like sources at startup, parses values into settings structs, and keeps raw reads at the application edge. The crate fits services that separate domain code from infrastructure code.

The library is small by design. It gives each part one job, and it keeps those jobs visible in tests. An application defines a settings struct, then uses field specs to bind each value. The field spec handles parsing, validation, and safe error text.

Design Model

Configuration loading has three steps. First, an Environment adapter reads raw text. Next, a field spec such as StringVar or U16Var parses one value. Then ParameterSource builds a settings struct from typed values.

Binder sits between the source and the spec. It coordinates the read and the parse, but it does not contain service rules. This split keeps startup code clear. Domain code receives typed settings and never needs std::env.

The crate forbids unsafe code and denies missing public docs. Local checks run formatting, Cargo check, Clippy with warnings denied, tests, doc tests, and docs.rs-style docs.

Safe Defaults

Envbind treats values as sensitive by default. Validation details stay hidden until a field is marked with .sensitive(false). Parse errors name the variable and the expected type. They do not print the raw value.

The crate limits input size before costly parsing. General raw values stop at 1 MiB. JsonVar stops at 64 KiB. B64DecodedStringVar stops at 1 MiB of decoded text. ListVar stops at 1024 items. Larger values require an explicit limit setter.

Type Default limit Override
StringVar 1 MiB raw text .max_bytes(...)
OptionalStringVar 1 MiB raw text .max_bytes(...)
BoolVar 1 MiB raw text No byte override
IntVar 1 MiB raw text No byte override
FloatVar 1 MiB raw text No byte override
U16Var 1 MiB raw text No byte override
EnumVar 1 MiB raw text No byte override
JsonVar 64 KiB raw JSON .max_bytes(...)
B64DecodedStringVar 1 MiB decoded text .max_decoded_bytes(...)
ListVar 1 MiB raw text and 1024 items .max_items(...)

Installation

The crates.io package name is envbind. The GitHub repository uses the OneTesseractInMultiverse namespace. Rust code imports the crate as envbind.

[dependencies]
envbind = "0.1.0"

Then import the Rust crate name:

use envbind::{Binder, Environment, ParameterSource, StringVar};

Quick Start

This example binds six fields into a single settings struct. It uses a map source for a deterministic example. Production code normally calls Settings::from_process_environment() at startup.

use envbind::{
    B64DecodedStringVar, Binder, BindingExt, BoolVar, Environment, IntVar, ListVar, MapEnvironment,
    ParameterSource, StringVar, validators,
};

#[derive(Debug, Clone, PartialEq, Eq)]
struct Settings {
    host: String,
    port: i64,
    service_name: String,
    tracing_enabled: bool,
    hosts: Vec<String>,
    certificate: String,
}

impl ParameterSource for Settings {
    fn bind<E: Environment>(binder: &Binder<E>) -> Result<Self, envbind::BindError> {
        Ok(Settings {
            host: binder.bind(&StringVar::new("SERVICE_HOST").default("127.0.0.1"))?,
            port: binder.bind(
                &IntVar::new("SERVICE_PORT")
                    .default(8080)
                    .sensitive(false)
                    .validate(validators::in_range(1, 65_535)),
            )?,
            service_name: binder.bind(&StringVar::new("SERVICE_NAME").default("api"))?,
            tracing_enabled: binder.bind(&BoolVar::new("SERVICE_TRACING").default(false))?,
            hosts: binder.bind(&ListVar::strings("SERVICE_HOSTS").default(vec![
                "localhost".to_owned(),
            ]))?,
            certificate: binder.bind(&B64DecodedStringVar::new("SERVICE_CERT").optional())?
                .unwrap_or_default(),
        })
    }
}

fn main() -> Result<(), envbind::BindError> {
    let environment = MapEnvironment::from_pairs([
        ("SERVICE_HOST", "localhost"),
        ("SERVICE_PORT", "9090"),
        ("SERVICE_HOSTS", "api,worker"),
        ("SERVICE_CERT", "Y2VydGlmaWNhdGU="),
    ]);

    let settings = Settings::from_environment(environment)?;
    assert_eq!(settings.port, 9090);
    Ok(())
}

Load the process environment at the application boundary:

let settings = Settings::from_process_environment() ?;

Public API

The public API centers on a small set of traits and field specs.

Item Role
Environment Reads raw values by name.
ProcessEnvironment Reads from the process environment.
MapEnvironment Gives tests and examples in-memory values.
Binder Coordinates one binding spec with one environment.
ParameterSource Builds an application settings struct.
BindError Reports stable binding failures.
ValidationError Carries safe validation text.

Field specs cover the common startup types: StringVar, OptionalStringVar, BoolVar, IntVar, FloatVar, ListVar, JsonVar, EnumVar, B64DecodedStringVar, and U16Var.

Every field spec supports .default(...), .allow_empty(), .sensitive(false), and .validate(...). BindingExt::optional() wraps any binding spec and returns None for missing or empty input.

Field Behavior

StringVar binds a required string. Missing values fail without a default. Empty strings act as missing by default. Whitespace-only strings remain input. Defaults return before validation.

# use envbind::{Binder, MapEnvironment, StringVar};
let host = Binder::new(MapEnvironment::from_pairs([("HOST", "localhost")]))
.bind( & StringVar::new("HOST")) ?;
assert_eq!(host, "localhost");
# Ok::<(), envbind::BindError>(())

OptionalStringVar returns Option<String>. Missing values return None. Empty strings return None by default.

# use envbind::{Binder, MapEnvironment, OptionalStringVar};
let token = Binder::new(MapEnvironment::new()).bind( & OptionalStringVar::new("TOKEN")) ?;
assert_eq!(token, None);
# Ok::<(), envbind::BindError>(())

BoolVar accepts 1, true, yes, on, y, and t for true. It accepts 0, false, no, off, n, and f for false. Matching ignores ASCII case after trimming.

# use envbind::{Binder, BoolVar, MapEnvironment};
let enabled = Binder::new(MapEnvironment::from_pairs([("TRACE", "yes")]))
.bind( & BoolVar::new("TRACE")) ?;
assert!(enabled);
# Ok::<(), envbind::BindError>(())

U16Var parses a u16 and runs typed validators. Use it for ports and other bounded unsigned values.

# use envbind::{Binder, MapEnvironment, U16Var, validators};
let port = Binder::new(MapEnvironment::from_pairs([("PORT", "8080")]))
.bind( & U16Var::new("PORT").validate(validators::u16_in_range(1, 65_535))) ?;
assert_eq!(port, 8080);
# Ok::<(), envbind::BindError>(())

Python EnvBind Parity

Rust uses typed binding specs instead of Python descriptors. The field set maps to the Python package in a direct way.

Python field Rust field
StringEnv StringVar
IntEnv IntVar
FloatEnv FloatVar
BooleanEnv BoolVar
ListEnv ListVar
JSONEnv JsonVar
EnumEnv EnumVar
B64DecodedStringEnv B64DecodedStringVar

Use .default(...) for fallback values. Use .allow_empty() to parse empty text. Set .sensitive(false) only for display-safe validation details. Use .optional() from BindingExt for missing values that return None.

The enum helpers accept both enum names and string values. The list helpers cover string, integer, float, boolean, u16, enum-like, and custom item parsers.

For exact enum labels, call .case_sensitive(). For extra labels, call .alias(...).

Error Handling

Binding failures return BindError. Each variant maps to a stable error_code() string. Use it for logging, metrics, and tests.

Display text does not include raw environment values. Callers often bind credentials or private deployment settings.

# use envbind::{Binder, MapEnvironment, U16Var};
let error_code = Binder::new(MapEnvironment::from_pairs([("PORT", "abc")]))
.bind( & U16Var::new("PORT"))
.map_err( | error| error.error_code());

assert_eq!(error_code, Err("parse_variable"));
Code Meaning
missing_variable A required variable was absent.
empty_variable A required variable was empty.
environment_error The adapter failed to read a value.
invalid_boolean A boolean value used an unknown token.
parse_variable A value did not match the target type.
validation_failed A typed value failed validation.
value_too_large A raw value exceeded its byte limit.

Repository Documentation

The repository includes focused documents for design, use, tests, release work, and project policy. Start with API Guide for usage. Read Architecture for design rules.

Reference files:

Local Commands

make test
make test-doc
make build
make check
make lint
make fmt
make doc
make verify
make package-list
make package
make publish-dry-run

make verify is the main local gate. It checks formatting, type checks the crate, runs Clippy, runs tests, runs doc tests, and builds docs.rs-style docs.

Publishing Readiness

Run these commands before a release:

make verify
make package-list
make package
make publish-dry-run

The package includes source, tests, examples, docs, and the MIT license. It excludes build output and machine-specific files.

License

Licensed under the MIT License. See LICENSE-MIT.