use arcature::prelude::*;
pub struct Config {
pub app_name: String,
pub app_url: String,
pub app_key: Vec<u8>,
pub bind_addr: String,
pub port: u16,
pub production: bool,
pub mail_from: String,
pub database: DatabaseConfig,
pub cache: Option<CacheConfig>,
pub storage: StorageConfig,
pub mail: SmtpConfig,
}
pub fn load() -> Result<Config> {
Ok(Config {
app_name: env_or("APP_NAME", "__PROJECT_NAME__"),
app_url: env_or("APP_URL", "http://127.0.0.1:3000")
.trim_end_matches('/')
.to_string(),
app_key: app_key()?,
bind_addr: env_or("APP_BIND", "127.0.0.1"),
port: arcature::config::env_parsed("APP_PORT", 3000),
production: !cfg!(debug_assertions),
mail_from: env_or("MAIL_FROM", "noreply@localhost"),
database: DatabaseConfig::new(&env_or("DATABASE_URL", "__DATABASE_URL__"))?,
cache: cache()?,
storage: StorageConfig::fs(&env_or("STORAGE_ROOT", "./storage"))
.map_err(|e| Error::Config(e.to_string()))?,
mail: SmtpConfig::from_url(&env_or("MAIL_URL", "smtp://127.0.0.1:1025"))?,
})
}
fn cache() -> Result<Option<CacheConfig>> {
let url = std::env::var("REDIS_URL").unwrap_or_default();
let url = url.trim();
if url.is_empty() {
return Ok(None);
}
CacheConfig::new(url)
.map(Some)
.map_err(|e| Error::Config(e.to_string()))
}
fn app_key() -> Result<Vec<u8>> {
let hex = std::env::var("APP_KEY").unwrap_or_default();
if hex.trim().is_empty() {
return Err(Error::Config(
"APP_KEY is empty; run `arc key:generate` to write one into .env".to_string(),
));
}
decode_hex(hex.trim()).ok_or_else(|| {
Error::Config(
"APP_KEY is not 128 lowercase hex characters; run `arc key:generate` \
to replace it"
.to_string(),
)
})
}
fn decode_hex(hex: &str) -> Option<Vec<u8>> {
if hex.len() % 2 != 0 {
return None;
}
hex.as_bytes()
.chunks(2)
.map(|pair| {
let text = std::str::from_utf8(pair).ok()?;
u8::from_str_radix(text, 16).ok()
})
.collect()
}