use std::net::SocketAddr;
use anyhow::{Context, Result};
const DEFAULT_ADDR: &str = "0.0.0.0:8080";
#[derive(Debug, Clone)]
pub struct Config {
pub addr: SocketAddr,
pub database_url: String,
pub agents: String,
}
impl Config {
pub fn from_env() -> Result<Self> {
Self::from_lookup(|key| std::env::var(key).ok())
}
fn from_lookup(lookup: impl Fn(&str) -> Option<String>) -> Result<Self> {
let addr = lookup("KASL_SERVER_ADDR").unwrap_or_else(|| DEFAULT_ADDR.to_string());
let addr = addr
.parse()
.with_context(|| format!("KASL_SERVER_ADDR is not a valid socket address: {addr}"))?;
let database_url = lookup("DATABASE_URL").context("DATABASE_URL is not set (e.g. postgres://kasl:kasl@localhost:5432/kasl)")?;
let agents = lookup("KASL_AGENTS").unwrap_or_default();
Ok(Self { addr, database_url, agents })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn env<'a>(pairs: &'a [(&'a str, &'a str)]) -> impl Fn(&str) -> Option<String> + 'a {
move |key| pairs.iter().find(|(k, _)| *k == key).map(|(_, v)| v.to_string())
}
#[test]
fn defaults_the_bind_address() {
let config = Config::from_lookup(env(&[("DATABASE_URL", "postgres://localhost/kasl")])).expect("config should build with only DATABASE_URL set");
assert_eq!(config.addr, DEFAULT_ADDR.parse().unwrap());
assert_eq!(config.database_url, "postgres://localhost/kasl");
}
#[test]
fn reads_the_bind_address_override() {
let config = Config::from_lookup(env(&[("DATABASE_URL", "postgres://localhost/kasl"), ("KASL_SERVER_ADDR", "127.0.0.1:9090")]))
.expect("config should accept a valid override");
assert_eq!(config.addr, "127.0.0.1:9090".parse().unwrap());
}
#[test]
fn requires_database_url() {
let error = Config::from_lookup(env(&[])).unwrap_err();
assert!(error.to_string().contains("DATABASE_URL"));
}
#[test]
fn rejects_a_malformed_bind_address() {
let error = Config::from_lookup(env(&[("DATABASE_URL", "postgres://localhost/kasl"), ("KASL_SERVER_ADDR", "not-an-address")])).unwrap_err();
assert!(error.to_string().contains("KASL_SERVER_ADDR"));
}
}