An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 124 lines 4.0 kB view raw
1use std::collections::HashMap; 2use std::path::Path; 3 4use crate::config::{apply_env_overrides, validate_and_build, Config, ConfigError, RawConfig}; 5 6/// Standard OpenTelemetry env vars we read in addition to our `EZPDS_*` prefix. 7const OTEL_ENV_KEYS: &[&str] = &["OTEL_SERVICE_NAME"]; 8 9/// Collect `EZPDS_*` env vars and selected OTel standard vars from the process environment, 10/// rejecting any with non-UTF-8 values rather than panicking. 11fn collect_ezpds_env() -> Result<HashMap<String, String>, ConfigError> { 12 let mut map = HashMap::new(); 13 for (key_os, val_os) in std::env::vars_os() { 14 let key = match key_os.to_str() { 15 Some(k) if k.starts_with("EZPDS_") || OTEL_ENV_KEYS.contains(&k) => k.to_owned(), 16 _ => continue, 17 }; 18 let val = val_os.into_string().map_err(|_| { 19 ConfigError::Invalid(format!( 20 "environment variable {key} contains non-UTF-8 data" 21 )) 22 })?; 23 map.insert(key, val); 24 } 25 Ok(map) 26} 27 28/// Load [`Config`] from a TOML file with an explicit environment map. 29/// 30/// Prefer [`load_config`] for production use. This variant is `pub(crate)` so tests can pass a 31/// controlled environment without leaking real `EZPDS_*` vars. 32pub(crate) fn load_config_with_env( 33 path: &Path, 34 env: &HashMap<String, String>, 35) -> Result<Config, ConfigError> { 36 let contents = std::fs::read_to_string(path).map_err(|source| ConfigError::Io { 37 path: path.to_owned(), 38 source, 39 })?; 40 let raw: RawConfig = toml::from_str(&contents)?; 41 let raw = apply_env_overrides(raw, env)?; 42 validate_and_build(raw) 43} 44 45/// Load [`Config`] from a TOML file, applying `EZPDS_*` environment variable overrides. 46pub fn load_config(path: &Path) -> Result<Config, ConfigError> { 47 let env = collect_ezpds_env()?; 48 load_config_with_env(path, &env) 49} 50 51#[cfg(test)] 52mod tests { 53 use super::*; 54 use std::io::Write; 55 56 fn empty_env() -> HashMap<String, String> { 57 HashMap::new() 58 } 59 60 #[test] 61 fn loads_config_from_file() { 62 let mut tmp = tempfile::NamedTempFile::new().unwrap(); 63 writeln!( 64 tmp, 65 r#"data_dir = "/var/pds" 66public_url = "https://pds.example.com" 67available_user_domains = ["example.com"]"# 68 ) 69 .unwrap(); 70 71 let config = load_config_with_env(tmp.path(), &empty_env()).unwrap(); 72 73 assert_eq!(config.public_url, "https://pds.example.com"); 74 assert_eq!(config.bind_address, "0.0.0.0"); 75 assert_eq!(config.port, 8080); 76 } 77 78 #[test] 79 fn loads_minimal_valid_toml_produces_missing_field_error() { 80 // An empty file is valid TOML but missing required fields. 81 let tmp = tempfile::NamedTempFile::new().unwrap(); 82 83 let err = load_config_with_env(tmp.path(), &empty_env()).unwrap_err(); 84 85 assert!(matches!( 86 err, 87 ConfigError::MissingField { field: "data_dir" } 88 )); 89 } 90 91 #[test] 92 fn env_overrides_applied_from_file() { 93 let mut tmp = tempfile::NamedTempFile::new().unwrap(); 94 writeln!( 95 tmp, 96 r#"data_dir = "/var/pds" 97public_url = "https://pds.example.com" 98available_user_domains = ["example.com"]"# 99 ) 100 .unwrap(); 101 let env = HashMap::from([("EZPDS_PORT".to_string(), "9999".to_string())]); 102 103 let config = load_config_with_env(tmp.path(), &env).unwrap(); 104 105 assert_eq!(config.port, 9999); 106 } 107 108 #[test] 109 fn returns_error_for_missing_file() { 110 let result = load_config_with_env(Path::new("/nonexistent/relay.toml"), &empty_env()); 111 112 assert!(matches!(result, Err(ConfigError::Io { .. }))); 113 } 114 115 #[test] 116 fn returns_error_for_invalid_toml() { 117 let mut tmp = tempfile::NamedTempFile::new().unwrap(); 118 writeln!(tmp, "not valid toml = [[[").unwrap(); 119 120 let result = load_config_with_env(tmp.path(), &empty_env()); 121 122 assert!(matches!(result, Err(ConfigError::Parse(_)))); 123 } 124}