use confroid::{ConfroidError, from_pairs};
#[derive(confroid::Config, Debug)]
struct HttpConfig {
#[confroid(default = 8080)]
port: u16,
#[confroid(example = "localhost")]
host: String,
}
const DEFAULT_NAME: &str = "World";
#[derive(confroid::Config, Debug)]
struct Config {
http: HttpConfig,
#[confroid(default = "Hello")]
greeting: String,
#[confroid(default = DEFAULT_NAME, example = "John")]
name: String,
}
#[test]
fn all_defaults_with_required_host() {
let config: Config = from_pairs([("HTTP__HOST", "localhost")]).unwrap();
assert_eq!(config.http.port, 8080);
assert_eq!(config.http.host, "localhost");
assert_eq!(config.greeting, "Hello");
assert_eq!(config.name, "World");
}
#[test]
fn everything_overridden() {
let config: Config = from_pairs([
("HTTP__PORT", "9090"),
("HTTP__HOST", "example.com"),
("GREETING", "Hi"),
("NAME", "Bob"),
])
.unwrap();
assert_eq!(config.http.port, 9090);
assert_eq!(config.http.host, "example.com");
assert_eq!(config.greeting, "Hi");
assert_eq!(config.name, "Bob");
}
#[test]
fn missing_required_field_reports_var_and_path() {
let err = from_pairs::<Config, _, _, _>([("GREETING", "Hi")]).unwrap_err();
assert!(matches!(
err,
ConfroidError::EnvVarNotFound { var_name, field }
if var_name == "HTTP__HOST" && field == "http.host"
));
}
#[test]
fn invalid_value_reports_var_path_and_value() {
let err =
from_pairs::<Config, _, _, _>([("HTTP__HOST", "h"), ("HTTP__PORT", "abc")]).unwrap_err();
match err {
ConfroidError::EnvVarInvalid {
var_name,
field,
value,
..
} => {
assert_eq!(var_name, "HTTP__PORT");
assert_eq!(field, "http.port");
assert_eq!(value, "abc");
}
other => panic!("expected EnvVarInvalid, got {other:?}"),
}
}
#[derive(confroid::Config, Debug)]
struct People {
people: std::collections::HashMap<String, Person>,
}
#[derive(confroid::Config, Debug)]
struct Person {
name: String,
age: u8,
}
#[test]
fn hashmap_of_structs() {
let config: People = from_pairs([
("PEOPLE__alice__NAME", "Alice"),
("PEOPLE__alice__AGE", "30"),
("PEOPLE__bob__NAME", "Bob"),
("PEOPLE__bob__AGE", "25"),
])
.unwrap();
assert_eq!(config.people.len(), 2);
assert_eq!(config.people.get("alice").unwrap().name, "Alice");
assert_eq!(config.people.get("alice").unwrap().age, 30);
assert_eq!(config.people.get("bob").unwrap().name, "Bob");
assert_eq!(config.people.get("bob").unwrap().age, 25);
}
#[derive(confroid::Config, Debug)]
struct Names {
names: Vec<String>,
}
#[test]
fn indexed_vector() {
let config: Names = from_pairs([("NAMES__0", "Alice"), ("NAMES__1", "Bob")]).unwrap();
assert_eq!(config.names.len(), 2);
assert_eq!(config.names[0], "Alice");
assert_eq!(config.names[1], "Bob");
}
#[derive(confroid::Config, Debug)]
struct AutoNames {
#[confroid(auto_vec)]
names: Vec<String>,
}
#[derive(confroid::Config, Debug)]
struct AutoNamesSemi {
#[confroid(auto_vec, auto_vec_delimiter = ";")]
names: Vec<String>,
}
#[test]
fn delimited_vector_default_comma() {
let config: AutoNames = from_pairs([("NAMES", "Alice,Bob")]).unwrap();
assert_eq!(config.names, vec!["Alice".to_string(), "Bob".to_string()]);
}
#[test]
fn delimited_vector_custom_delimiter() {
let config: AutoNamesSemi = from_pairs([("NAMES", "Alice;Bob")]).unwrap();
assert_eq!(config.names, vec!["Alice".to_string(), "Bob".to_string()]);
}
#[derive(confroid::Config, Debug)]
struct Servers {
servers: Vec<Server>,
}
#[derive(confroid::Config, Debug)]
struct Server {
host: String,
port: u16,
}
#[test]
fn vector_of_structs() {
let config: Servers = from_pairs([
("SERVERS__0__HOST", "a.example.com"),
("SERVERS__0__PORT", "80"),
("SERVERS__1__HOST", "b.example.com"),
("SERVERS__1__PORT", "443"),
])
.unwrap();
assert_eq!(config.servers.len(), 2);
assert_eq!(config.servers[0].host, "a.example.com");
assert_eq!(config.servers[1].port, 443);
}
#[derive(confroid::Config, Debug)]
struct Optional {
name: Option<String>,
}
#[test]
fn option_absent_is_none() {
let config: Optional = from_pairs::<Optional, _, _, _>(Vec::<(String, String)>::new()).unwrap();
assert_eq!(config.name, None);
}
#[test]
fn option_empty_is_none() {
let config: Optional = from_pairs([("NAME", "")]).unwrap();
assert_eq!(config.name, None);
}
#[test]
fn required_string_preserves_an_empty_value() {
#[derive(confroid::Config, Debug)]
struct Required {
name: String,
}
let config: Required = from_pairs([("NAME", "")]).unwrap();
assert_eq!(config.name, "");
}
#[test]
fn option_set_is_some_value() {
let config: Optional = from_pairs([("NAME", "Alice")]).unwrap();
assert_eq!(config.name, Some("Alice".to_string()));
}
#[test]
fn option_empty_uses_its_default() {
#[derive(confroid::Config, Debug)]
struct WithDefault {
#[confroid(default = Some(String::from("anonymous")))]
name: Option<String>,
}
let config: WithDefault = from_pairs([("NAME", "")]).unwrap();
assert_eq!(config.name.as_deref(), Some("anonymous"));
}