use std::{
env,
net::{AddrParseError, IpAddr, Ipv6Addr},
num::ParseIntError,
};
use url::Url;
pub struct Env {
address: IpAddr,
base_url: Url,
log_level: log::Level,
pds_url: Url,
http_port: u16,
site_name: String,
}
#[derive(Debug, Clone, Copy)]
pub enum EnvKey {
Address,
Host,
HttpPort,
LogLevel,
PdsUrl,
SiteName,
}
impl AsRef<std::ffi::OsStr> for EnvKey {
fn as_ref(&self) -> &std::ffi::OsStr {
match self {
Self::Address => "ADDRESS".as_ref(),
Self::Host => "HOST".as_ref(),
Self::HttpPort => "HTTP_PORT".as_ref(),
Self::LogLevel => "LOG_LEVEL".as_ref(),
Self::PdsUrl => "PDS_URL".as_ref(),
Self::SiteName => "SITE_NAME".as_ref(),
}
}
}
impl core::fmt::Display for EnvKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_ref().display())
}
}
impl Env {
pub fn new() -> Result<Self, EnvError> {
#[cfg(debug_assertions)]
{
_ = dotenvy::dotenv();
}
let key = EnvKey::Address;
let address: IpAddr = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
Err(std::env::VarError::NotPresent) => Ipv6Addr::UNSPECIFIED.into(),
Ok(a) => a.parse().map_err(|e| EnvError::IpAddr(key, e))?,
};
let key = EnvKey::HttpPort;
let http_port: u16 = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
Err(std::env::VarError::NotPresent) => 8080,
Ok(p) => p.parse().map_err(|e| EnvError::Port(key, e))?,
};
let key = EnvKey::Host;
let base_url: Url = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
Err(std::env::VarError::NotPresent) => return Err(EnvError::Missing(key)),
Ok(d) => {
let domain = url::Host::parse(&d).map_err(|e| EnvError::Host(key, e))?;
format!("https://{domain}")
.parse()
.map_err(|e| EnvError::Url(key, e))?
}
};
let key = EnvKey::LogLevel;
let log_level: log::Level = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
#[cfg(debug_assertions)]
Err(std::env::VarError::NotPresent) => log::Level::Debug,
#[cfg(not(debug_assertions))]
Err(std::env::VarError::NotPresent) => log::Level::Warn,
Ok(l) => l.parse().map_err(|_| EnvError::LogLevel(key))?,
};
let key = EnvKey::PdsUrl;
let pds_url: Url = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
Err(std::env::VarError::NotPresent) => return Err(EnvError::Missing(key)),
Ok(u) => u.parse().map_err(|e| EnvError::Url(key, e))?,
};
let key = EnvKey::SiteName;
let site_name: String = match env::var(key) {
Err(std::env::VarError::NotUnicode(_)) => return Err(EnvError::NotUnicode(key)),
Err(std::env::VarError::NotPresent) => "AT Proto Static".to_owned(),
Ok(s) => s.trim().to_owned(),
};
Ok(Self {
address,
base_url,
http_port,
log_level,
pds_url,
site_name,
})
}
pub const fn address(&self) -> IpAddr {
self.address
}
pub const fn base_url(&self) -> &Url {
&self.base_url
}
pub const fn http_port(&self) -> u16 {
self.http_port
}
pub const fn log_level(&self) -> log::Level {
self.log_level
}
pub const fn pds_url(&self) -> &Url {
&self.pds_url
}
pub const fn site_name(&self) -> &str {
self.site_name.as_str()
}
}
#[derive(Debug)]
pub enum EnvError {
Host(EnvKey, url::ParseError),
IpAddr(EnvKey, AddrParseError),
LogLevel(EnvKey),
Missing(EnvKey),
NotUnicode(EnvKey),
Port(EnvKey, ParseIntError),
Url(EnvKey, url::ParseError),
}
impl core::fmt::Display for EnvError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "value for env key ")?;
match self {
Self::Host(key, err) => write!(f, "{key} is not a domain name: {err}"),
Self::IpAddr(key, err) => write!(f, "{key} is not a valid IP address: {err}"),
Self::LogLevel(key) => write!(f, "{key} is not a valid log level"),
Self::Missing(key) => write!(f, "{key} not found"),
Self::NotUnicode(key) => write!(f, "{key} is not unicode"),
Self::Port(key, err) => write!(f, "{key} is not a port number: {err}"),
Self::Url(key, err) => write!(f, "{key} is not a URL: {err}"),
}
}
}
impl core::error::Error for EnvError {}