ground-env 0.2.0

Parse env variables by defining a struct
Documentation
use super::*;

fn test_env<T: FromEnv>(env: &[(&'static str, &'static str)]) -> Result<T> {
    let mut ctx = Context::empty();
    for (k, v) in env {
        ctx.env.insert(String::from(*k), Ok((*v).into()));
    }
    T::from_ctx(&mut ctx)
}

#[test]
fn test_simple() {
    #[derive(FromEnv)]
    #[env(root = "crate")]
    struct Test {
        text: String,
        optional_text_missing: Option<String>,
        optional_text_present: Option<String>,
        number: i64,
    }

    let test = test_env::<Test>(&[
        ("TEXT", "Hello"),
        ("OPTIONAL_TEXT_PRESENT", "World"),
        ("NUMBER", "-42"),
    ])
        .unwrap();
    assert_eq!(test.text, "Hello");
    assert!(test.optional_text_missing.is_none());
    assert_eq!(test.optional_text_present.as_deref(), Some("World"));
    assert_eq!(test.number, -42);

    assert!(test_env::<Test>(&[("TEXT", "Hello"), ("OPTIONAL_TEXT_PRESENT", "World"), ]).is_err());
}

#[test]
fn test_rename() {
    #[derive(FromEnv)]
    #[env(root = "crate")]
    struct Test {
        #[env(rename = "NUMBER")]
        text: String,
    }

    let test = test_env::<Test>(&[("NUMBER", "Hello")]).unwrap();
    assert_eq!(test.text, "Hello");
}

#[test]
fn test_defaults() {
    #[derive(FromEnv)]
    #[env(root = "crate")]
    struct Test {
        #[env(default = "Hello, World!")]
        text: String,
        #[env(default)]
        number: i64,
        #[env(default)]
        optional_text: Option<String>,
        #[env(default = "asd")]
        optional_text_but_defaulted: Option<String>,
    }

    let test = test_env::<Test>(&[]).unwrap();
    assert_eq!(test.text, "Hello, World!");
    assert_eq!(test.number, 0);
    assert!(test.optional_text.is_none());
    assert!(test.optional_text_but_defaulted.is_some());
}

#[test]
fn test_flatten() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct TestText {
        text: String,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct TestNumber {
        number: i64,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Test {
        #[env(flatten)]
        text: TestText,
        #[env(flatten = "TEST_")]
        number: TestNumber,
    }

    let test = test_env::<Test>(&[("TEXT", "World"), ("TEST_NUMBER", "42")]).unwrap();
    assert_eq!(test.text.text, "World");
    assert_eq!(test.number.number, 42);
}

#[test]
fn test_vec() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Test {
        #[env(delimiter = " ")]
        text: Vec<String>,
        number: Vec<i64>,
        #[env(default)]
        empty: Vec<i64>,
    }

    let test = test_env::<Test>(&[("TEXT", "Hello World"), ("NUMBER", "1,2,3,4")]).unwrap();
    assert_eq!(test.text, vec!["Hello", "World"]);
    assert_eq!(test.number, vec![1, 2, 3, 4]);
    assert_eq!(test.empty, vec![]);
}

#[test]
fn test_invalid_data_type() {
    #[derive(FromEnv)]
    #[env(root = "crate")]
    #[allow(dead_code)]
    struct Test {
        number: i64,
    }

    assert!(test_env::<Test>(&[("NUMBER", "not_a_number")]).is_err());
}

#[test]
fn test_nested_structures() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Inner {
        inner_text: String,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Outer {
        outer_text: String,
        #[env(flatten)]
        inner: Inner,
    }

    let test = test_env::<Outer>(&[("OUTER_TEXT", "Hello"), ("INNER_TEXT", "World")]).unwrap();
    assert_eq!(test.outer_text, "Hello");
    assert_eq!(test.inner.inner_text, "World");
}

#[test]
fn test_nested_nested_structures() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Inner {
        inner_text: String,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Middle {
        middle_text: String,
        #[env(flatten)]
        inner: Inner,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Outer {
        outer_text: String,
        #[env(flatten)]
        middle: Middle,
    }

    let test = test_env::<Outer>(&[("OUTER_TEXT", "Good"), ("MIDDLE_TEXT", "Morning"), ("INNER_TEXT", "World")]).unwrap();
    assert_eq!(test.outer_text, "Good");
    assert_eq!(test.middle.middle_text, "Morning");
    assert_eq!(test.middle.inner.inner_text, "World");
}

#[test]
fn test_nested_nested_with_prefix_structures() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Inner {
        text: String,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Middle {
        text: String,
        #[env(flatten = "INNER_")]
        inner: Inner,
    }

    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Outer {
        outer_text: String,
        #[env(flatten = "MID_")]
        middle: Middle,
    }

    let test = test_env::<Outer>(&[("OUTER_TEXT", "Good"), ("MID_TEXT", "Morning"), ("MID_INNER_TEXT", "World")]).unwrap();
    assert_eq!(test.outer_text, "Good");
    assert_eq!(test.middle.text, "Morning");
    assert_eq!(test.middle.inner.text, "World");
}

#[test]
fn test_missing_required_fields() {
    #[derive(FromEnv)]
    #[env(root = "crate")]
    #[allow(dead_code)]
    struct Test {
        text: String,
    }

    assert!(test_env::<Test>(&[]).is_err());
}

#[test]
fn test_different_delimiters() {
    #[derive(Debug, FromEnv)]
    #[env(root = "crate")]
    struct Test {
        #[env(delimiter = ",")]
        text: Vec<String>,
    }

    let test = test_env::<Test>(&[("TEXT", "Hello,World")]).unwrap();
    assert_eq!(test.text, vec!["Hello", "World"]);
}