adotenv 0.9.2

A tooling-grade .env parser: literal values (no variable expansion), exact byte spans, and edits that leave the rest of the file untouched.
Documentation
use alloc::{
    string::{String, ToString},
    vec::Vec,
};
use serde::Deserialize;

use crate::{parse_strict, parse_with, BareKeys, Dialect};

#[derive(Debug, Deserialize, PartialEq)]
struct Config {
    host: String,
    port: u16,
    debug: bool,
    empty: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct Optional {
    present: Option<String>,
    missing: Option<String>,
}

#[test]
fn deserializes_literal_structs_and_primitives() {
    let doc =
        parse_strict("host=localhost\nport=8080\ndebug=true\nempty=\nunknown=ignored\n").unwrap();
    let config: Config = doc.deserialize().unwrap();
    assert_eq!(
        config,
        Config {
            host: "localhost".to_string(),
            port: 8080,
            debug: true,
            empty: String::new(),
        }
    );
}

#[test]
fn duplicate_policy_and_optional_fields_are_applied() {
    let doc = parse_strict("present=value\npresent=later\n").unwrap();
    let optional: Optional = doc.deserialize().unwrap();
    assert_eq!(optional.present.as_deref(), Some("later"));
    assert_eq!(optional.missing, None);
}

#[test]
fn bare_empty_policy_is_visible_to_serde() {
    let dialect = Dialect {
        bare_keys: BareKeys::Empty,
        ..Dialect::PORTABLE
    };
    let (doc, _) = parse_with("host\nport=8080\ndebug=false\nempty=\n", dialect);
    let config: Config = doc.deserialize().unwrap();
    assert_eq!(config.host, "");
}

#[test]
fn value_errors_carry_the_source_line() {
    let doc = parse_strict("host=localhost\nport=not-a-number\ndebug=true\nempty=\n").unwrap();
    let error = doc.deserialize::<Config>().unwrap_err();
    assert_eq!(error.line(), 2);
    assert_eq!(error.key(), Some("port"));
    assert!(error.message().contains("u16"));
}

#[test]
fn missing_fields_and_bare_declarations_are_errors() {
    let doc = parse_strict("host=localhost\nport=8080\nempty=\n").unwrap();
    assert!(doc.deserialize::<Config>().is_err());

    let doc = parse_strict("host\nport=8080\ndebug=true\nempty=\n").unwrap();
    assert!(doc.deserialize::<Config>().is_err());
}

#[test]
fn quoted_values_are_decoded_before_serde() {
    let doc = parse_strict("host=\"local host\"\nport=8080\ndebug=true\nempty=''\n").unwrap();
    let config: Config = doc.deserialize().unwrap();
    assert_eq!(config.host, "local host");
    assert_eq!(config.empty, "");
}

#[test]
fn unsupported_nested_shapes_report_a_value_error() {
    #[derive(Debug, Deserialize)]
    #[allow(dead_code)]
    struct Nested {
        values: Vec<String>,
    }
    let doc = parse_strict("values=one\n").unwrap();
    let error = doc.deserialize::<Nested>().unwrap_err();
    assert_eq!(error.key(), Some("values"));
    assert!(error.message().contains("sequence"));
}