confroid 0.0.4

The n+1-st config reader for your environment-based configs.
Documentation
//! Documentation-generation tests (require the `docs` feature).
#![cfg(feature = "docs")]
#![allow(dead_code)] // fields exist for their derived docs, not for reading

use confroid::{env_example, markdown_table};
use std::collections::HashMap;

#[derive(confroid::Config, Debug)]
struct HttpConfig {
    #[confroid(default = 8080)]
    /// The port to listen on.
    port: u16,
    #[confroid(example = "localhost")]
    /// The host to listen on.
    host: String,
}

const DEFAULT_NAME: &str = "World";

#[derive(confroid::Config, Debug)]
struct Config {
    /// The HTTP configuration.
    http: HttpConfig,
    #[confroid(default = "Hello")]
    /// The greeting to use.
    greeting: String,
    #[confroid(default = DEFAULT_NAME, example = "John")]
    /// The name of the user.
    name: String,
}

#[test]
fn env_example_matches_expected_layout() {
    let expected = "\
# Config configuration

# The HTTP configuration.

# The port to listen on. (default: 8080)
# HTTP__PORT=8080
# The host to listen on. (example: localhost)
HTTP__HOST=

# The greeting to use. (default: Hello)
# GREETING=Hello

# The name of the user. (default: World, example: John)
# NAME=World
";
    assert_eq!(env_example::<Config>(), expected);
}

#[test]
fn markdown_table_lists_all_leaf_variables() {
    let expected = "\
| Variable | Description | Default | Example |
| --- | --- | --- | --- |
| `HTTP__PORT` | The port to listen on. | `8080` |  |
| `HTTP__HOST` | The host to listen on. |  | `localhost` |
| `GREETING` | The greeting to use. | `Hello` |  |
| `NAME` | The name of the user. | `World` | `John` |
";
    assert_eq!(markdown_table::<Config>(), expected);
}

#[test]
fn bare_default_renders_via_display() {
    #[derive(confroid::Config, Debug)]
    struct WithBare {
        #[confroid(default)]
        /// Retry count.
        retries: u16,
    }
    let out = env_example::<WithBare>();
    assert!(out.contains("# Retry count. (default: 0)"), "got:\n{out}");
    assert!(out.contains("# RETRIES=0"), "got:\n{out}");
}

#[test]
fn container_prefix_is_rendered() {
    #[derive(confroid::Config, Debug)]
    #[confroid(prefix = "GUPPY")]
    struct Prefixed {
        /// The port to listen on.
        port: u16,
    }

    let example = env_example::<Prefixed>();
    assert!(example.contains("GUPPY__PORT="), "got:\n{example}");

    let table = markdown_table::<Prefixed>();
    assert!(table.contains("`GUPPY__PORT`"), "got:\n{table}");
}

#[test]
fn hashmap_of_structs_documents_keyed_nested_fields() {
    #[derive(confroid::Config, Debug)]
    struct Person {
        /// The person's name.
        name: String,
        /// The person's age.
        age: u8,
    }

    #[derive(confroid::Config, Debug)]
    struct WithPeople {
        /// People indexed by identifier.
        people: HashMap<String, Person>,
    }

    let example = env_example::<WithPeople>();
    assert!(example.contains("PEOPLE__<key>__NAME="), "got:\n{example}");
    assert!(example.contains("PEOPLE__<key>__AGE="), "got:\n{example}");
    assert!(!example.contains("\nPEOPLE=\n"), "got:\n{example}");

    let table = markdown_table::<WithPeople>();
    assert!(
        table.contains("| `PEOPLE__<key>__NAME` | The person's name."),
        "got:\n{table}"
    );
    assert!(
        table.contains("| `PEOPLE__<key>__AGE` | The person's age."),
        "got:\n{table}"
    );
}

#[test]
fn hashmap_of_scalars_documents_key_placeholder() {
    #[derive(confroid::Config, Debug)]
    struct WithWeights {
        /// Weights indexed by name.
        weights: HashMap<String, u32>,
    }

    let example = env_example::<WithWeights>();
    assert!(example.contains("WEIGHTS__<key>="), "got:\n{example}");

    let table = markdown_table::<WithWeights>();
    assert!(table.contains("`WEIGHTS__<key>`"), "got:\n{table}");
}

#[test]
fn optional_and_defaulted_fields_are_commented_out() {
    #[derive(confroid::Config, Debug)]
    struct OptionalNested {
        required: String,
    }

    #[derive(confroid::Config, Debug)]
    struct WithOptionalAndDefaults {
        required: String,
        optional: Option<String>,
        nested: Option<OptionalNested>,
        #[confroid(default)]
        items: Vec<String>,
    }

    let example = env_example::<WithOptionalAndDefaults>();
    assert!(example.contains("\nREQUIRED=\n"), "got:\n{example}");
    assert!(example.contains("\n# OPTIONAL=\n"), "got:\n{example}");
    assert!(
        example.contains("\n# NESTED__REQUIRED=\n"),
        "got:\n{example}"
    );
    assert!(example.contains("\n# ITEMS=\n"), "got:\n{example}");
}