use figment::{
Figment,
providers::{Env, Format, Toml},
};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::path::PathBuf;
use crate::error::Result;
pub const DEFAULT_BIND_ADDR: &str = "127.0.0.1:3000";
#[derive(Debug, Clone, Deserialize)]
#[non_exhaustive]
pub struct Config {
pub service_did: String,
pub service_endpoint: String,
#[serde(default = "default_bind_addr")]
pub bind_addr: SocketAddr,
pub db_path: PathBuf,
pub signing_key_path: PathBuf,
#[serde(default)]
pub admin: AdminConfigToml,
#[serde(default)]
pub labeler: Option<LabelerConfigToml>,
#[serde(default)]
pub operator: Option<OperatorConfigToml>,
#[serde(default)]
pub retention: RetentionConfigToml,
#[serde(default)]
pub moderation_reasons: Option<BTreeMap<String, ReasonDefToml>>,
#[serde(default)]
pub strike_policy: Option<StrikePolicyToml>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ReasonDefToml {
pub base_weight: u32,
#[serde(default)]
pub severe: bool,
pub description: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct StrikePolicyToml {
#[serde(default = "default_good_standing_threshold")]
pub good_standing_threshold: u32,
#[serde(default = "default_dampening_curve")]
pub dampening_curve: Vec<u32>,
#[serde(default = "default_decay_function")]
pub decay_function: crate::moderation::policy::DecayFunction,
#[serde(default = "default_decay_window_days")]
pub decay_window_days: u32,
#[serde(default = "default_suspension_freezes_decay")]
pub suspension_freezes_decay: bool,
#[serde(default = "default_cache_freshness_window_seconds")]
pub cache_freshness_window_seconds: u32,
}
impl Default for StrikePolicyToml {
fn default() -> Self {
Self {
good_standing_threshold: default_good_standing_threshold(),
dampening_curve: default_dampening_curve(),
decay_function: default_decay_function(),
decay_window_days: default_decay_window_days(),
suspension_freezes_decay: default_suspension_freezes_decay(),
cache_freshness_window_seconds: default_cache_freshness_window_seconds(),
}
}
}
fn default_good_standing_threshold() -> u32 {
3
}
fn default_dampening_curve() -> Vec<u32> {
vec![1, 2]
}
fn default_decay_function() -> crate::moderation::policy::DecayFunction {
crate::moderation::policy::DecayFunction::Linear
}
fn default_decay_window_days() -> u32 {
90
}
fn default_suspension_freezes_decay() -> bool {
true
}
fn default_cache_freshness_window_seconds() -> u32 {
3600
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct AdminConfigToml {
#[serde(default)]
pub label_values: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, Deserialize)]
pub struct LabelerConfigToml {
pub label_values: Vec<String>,
#[serde(default)]
pub label_value_definitions: Vec<LabelValueDefinitionToml>,
#[serde(default)]
pub reason_types: Vec<String>,
#[serde(default)]
pub subject_types: Vec<String>,
#[serde(default)]
pub subject_collections: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, Deserialize)]
pub struct LabelValueDefinitionToml {
pub identifier: String,
pub severity: SeverityToml,
pub blurs: BlursToml,
#[serde(default)]
pub default_setting: Option<DefaultSettingToml>,
#[serde(default)]
pub adult_only: Option<bool>,
pub locales: Vec<LocaleToml>,
}
#[derive(Debug, Clone, Copy, serde::Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SeverityToml {
Inform,
Alert,
None,
}
#[derive(Debug, Clone, Copy, serde::Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BlursToml {
Content,
Media,
None,
}
#[derive(Debug, Clone, Copy, serde::Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DefaultSettingToml {
Ignore,
Warn,
Hide,
}
#[derive(Debug, Clone, serde::Serialize, Deserialize)]
pub struct LocaleToml {
pub lang: String,
pub name: String,
pub description: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RetentionConfigToml {
#[serde(default = "default_sweep_enabled")]
pub sweep_enabled: bool,
#[serde(default = "default_sweep_run_at_utc_hour")]
pub sweep_run_at_utc_hour: u8,
#[serde(default = "default_sweep_batch_size")]
pub sweep_batch_size: i64,
}
impl Default for RetentionConfigToml {
fn default() -> Self {
Self {
sweep_enabled: default_sweep_enabled(),
sweep_run_at_utc_hour: default_sweep_run_at_utc_hour(),
sweep_batch_size: default_sweep_batch_size(),
}
}
}
fn default_sweep_enabled() -> bool {
true
}
fn default_sweep_run_at_utc_hour() -> u8 {
4
}
fn default_sweep_batch_size() -> i64 {
1000
}
#[derive(Debug, Clone, Deserialize)]
pub struct OperatorConfigToml {
pub pds_url: String,
pub session_path: std::path::PathBuf,
}
fn default_bind_addr() -> SocketAddr {
DEFAULT_BIND_ADDR
.parse()
.expect("DEFAULT_BIND_ADDR is a valid socket address")
}
impl Config {
pub fn validate(&self) -> Result<()> {
url::Url::parse(&self.service_endpoint).map_err(|e| {
crate::error::Error::Signing(format!("config.service_endpoint is not a valid URL: {e}"))
})?;
if self.retention.sweep_run_at_utc_hour >= 24 {
return Err(crate::error::Error::Signing(format!(
"config.retention.sweep_run_at_utc_hour={} is out of range (0..=23)",
self.retention.sweep_run_at_utc_hour
)));
}
if self.retention.sweep_batch_size <= 0 {
return Err(crate::error::Error::Signing(format!(
"config.retention.sweep_batch_size={} must be > 0",
self.retention.sweep_batch_size
)));
}
let _ = crate::moderation::reasons::ReasonVocabulary::from_config(self)?;
let _ = crate::moderation::policy::StrikePolicy::from_config(self)?;
Ok(())
}
pub fn load() -> Result<Self> {
let toml_path: PathBuf = std::env::var_os("CAIRN_CONFIG")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("/etc/cairn/cairn.toml"));
Self::load_from(Some(&toml_path))
}
pub fn load_from(toml_path: Option<&std::path::Path>) -> Result<Self> {
let mut fig = Figment::new();
if let Some(p) = toml_path
&& p.is_file()
{
fig = fig.merge(Toml::file(p));
}
fig = fig.merge(Env::prefixed("CAIRN_"));
let cfg: Config = fig.extract()?;
cfg.validate()?;
Ok(cfg)
}
}
impl From<AdminConfigToml> for crate::AdminConfig {
fn from(t: AdminConfigToml) -> Self {
crate::AdminConfig {
label_values: t.label_values,
..Default::default()
}
}
}
impl From<RetentionConfigToml> for crate::RetentionConfig {
fn from(t: RetentionConfigToml) -> Self {
crate::RetentionConfig {
sweep_enabled: t.sweep_enabled,
sweep_run_at_utc_hour: t.sweep_run_at_utc_hour,
sweep_batch_size: t.sweep_batch_size,
}
}
}