mod value;
pub use value::FromEnvValue;
mod macros;
pub mod presets;
#[cfg(test)]
mod tests;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SettingsError {
pub errors: Vec<FieldError>,
}
impl fmt::Display for SettingsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let noun = if self.errors.len() == 1 { "problem" } else { "problems" };
write!(f, "invalid settings ({} {noun})", self.errors.len())?;
for error in &self.errors {
write!(f, "\n - {error}")?;
}
Ok(())
}
}
impl std::error::Error for SettingsError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldError {
pub var: String,
pub kind: FieldErrorKind,
}
impl fmt::Display for FieldError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
FieldErrorKind::Missing => write!(f, "{}: missing", self.var),
FieldErrorKind::Invalid { value: Some(value), message } => write!(f, "{}: invalid value {value:?}: {message}", self.var),
FieldErrorKind::Invalid { value: None, message } => write!(f, "{}: invalid value: {message}", self.var),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldErrorKind {
Missing,
Invalid { value: Option<String>, message: String },
}
pub fn env_name(prefix: Option<&str>, explicit: Option<&str>, field: &str) -> String {
match explicit {
Some(name) => name.to_string(),
None => {
let shouty = field.strip_prefix("r#").unwrap_or(field).to_ascii_uppercase();
match prefix {
Some(prefix) => format!("{prefix}_{shouty}"),
None => shouty,
}
}
}
}
pub fn lookup(source: &mut impl FnMut(&str) -> Option<String>, var: &str) -> Option<String> {
source(var).filter(|value| !value.is_empty())
}
pub fn require<T: FromEnvValue>(raw: Option<String>, var: &str, secret: bool, errors: &mut Vec<FieldError>) -> Option<T> {
match raw {
None => {
errors.push(FieldError {
var: var.to_string(),
kind: FieldErrorKind::Missing,
});
None
}
Some(raw) => parse_value(&raw, var, secret, errors),
}
}
pub fn optional<T: FromEnvValue>(raw: Option<String>, var: &str, secret: bool, errors: &mut Vec<FieldError>) -> Option<Option<T>> {
match raw {
None => Some(None),
Some(raw) => parse_value(&raw, var, secret, errors).map(Some),
}
}
pub fn with_default<T: FromEnvValue>(raw: Option<String>, default: &str, var: &str, secret: bool, errors: &mut Vec<FieldError>) -> Option<T> {
match raw {
Some(raw) => parse_value(&raw, var, secret, errors),
None => match T::from_env_value(default) {
Ok(value) => Some(value),
Err(message) => {
errors.push(FieldError {
var: var.to_string(),
kind: FieldErrorKind::Invalid {
value: Some(default.to_string()),
message: format!("invalid default: {message}"),
},
});
None
}
},
}
}
fn parse_value<T: FromEnvValue>(raw: &str, var: &str, secret: bool, errors: &mut Vec<FieldError>) -> Option<T> {
match T::from_env_value(raw) {
Ok(value) => Some(value),
Err(message) => {
errors.push(FieldError {
var: var.to_string(),
kind: FieldErrorKind::Invalid {
value: (!secret).then(|| raw.to_string()),
message,
},
});
None
}
}
}