Something went wrong. Try again.
Identities for entities did.bot
agent llm did
Something went wrong. Try again.
12 kB · 409 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410//! Everything that can be wrong with a configuration, said by name.//!//! Validation is separate from parsing because the two answer different//! questions. Parsing asks whether the file is the right shape; this asks//! whether what it says can be true — a profile naming a service that is not//! there, two services on one port, a scrobble host writing to a different//! server than the profile it fills a role in.//!//! Nothing here contacts anything. Whether a port is *actually* held, and by//! what, is a question for the machine rather than the file.
use std::collections::BTreeMap;use std::fmt;
use crate::config::{Config, ServiceKind};
/// One thing wrong with a configuration.#[derive(Debug, Clone, PartialEq, Eq)]pub enum Problem { /// A profile names a service that does not exist. NoSuchService { /// The profile that names it. profile: String, /// Which role it was named for. role: &'static str, /// The name that resolves to nothing. service: String, }, /// A profile names a service of the wrong kind for the role. WrongKind { /// The profile that names it. profile: String, /// Which role it was named for. role: &'static str, /// The service named. service: String, /// What it actually is. found: ServiceKind, }, /// A service names an upstream that does not exist. NoSuchUpstream { /// The service with the dangling reference. service: String, /// Which upstream. role: &'static str, /// The name that resolves to nothing. upstream: String, }, /// A profile's scrobble host writes to a different server than the /// profile's own. /// /// The failure this prevents is silent and confusing: the hook provisions /// an account on one server, and the scrobble carrying that account's DID /// is written to another, which has never heard of it. Disagrees { /// The profile. profile: String, /// The scrobble host it names. mcp: String, /// The server that host writes to. mcp_pds: String, /// The server the profile says accounts are on. profile_pds: String, }, /// Two services want the same address. PortClash { /// The address both want. listen: String, /// The services that want it. services: Vec<String>, }, /// A directory is bound to a profile that does not exist. NoSuchProfile { /// The bound directory. dir: String, /// The name that resolves to nothing. profile: String, }, /// There is no profile named `default`. /// /// Not fatal, but worth saying: an unbound directory resolves to it, and /// on a machine without one every directory nobody thought about stops /// working at once. NoDefaultProfile,}
impl fmt::Display for Problem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoSuchService { profile, role, service, } => write!( f, "profile {profile}: {role} names {service}, which does not exist" ), Self::WrongKind { profile, role, service, found, } => write!( f, "profile {profile}: {role} names {service}, which is a {found}" ), Self::NoSuchUpstream { service, role, upstream, } => write!( f, "service {service}: {role} names {upstream}, which does not exist" ), Self::Disagrees { profile, mcp, mcp_pds, profile_pds, } => write!( f, "profile {profile}: accounts are minted on {profile_pds} but {mcp} writes to \ {mcp_pds}, so every scrobble would name an account that server has never seen" ), Self::PortClash { listen, services } => { write!(f, "{} both want {listen}", services.join(" and ")) } Self::NoSuchProfile { dir, profile } => { write!( f, "{dir} is bound to profile {profile}, which does not exist" ) } Self::NoDefaultProfile => write!( f, "there is no profile named default, so any directory nobody bound resolves to \ nothing" ), } }}
/// Everything wrong with one configuration.pub type Problems = Vec<Problem>;
/// Checks a configuration against itself.pub(crate) fn check(config: &Config) -> Problems { let mut problems = Problems::new();
for (name, profile) in &config.profiles { let roles: [(&'static str, Option<&str>, ServiceKind); 5] = [ ("pds", Some(profile.pds.as_str()), ServiceKind::Pds), ("mcp", profile.mcp.as_deref(), ServiceKind::Mcp), ("index", profile.index.as_deref(), ServiceKind::Index), ("query", profile.query.as_deref(), ServiceKind::Query), ("web", profile.web.as_deref(), ServiceKind::Web), ]; for (role, named, expected) in roles { let Some(named) = named else { continue }; match config.services.get(named) { None => problems.push(Problem::NoSuchService { profile: name.clone(), role, service: named.to_owned(), }), Some(service) if service.kind != expected => problems.push(Problem::WrongKind { profile: name.clone(), role, service: named.to_owned(), found: service.kind, }), Some(_) => {} } }
// The one cross-role check, and the reason a profile names its server // explicitly rather than inheriting it from the scrobble host. if let Some(mcp_name) = profile.mcp.as_deref() { if let Some(mcp) = config.services.get(mcp_name) { if let Some(mcp_pds) = mcp.pds.as_deref() { if mcp_pds != profile.pds { problems.push(Problem::Disagrees { profile: name.clone(), mcp: mcp_name.to_owned(), mcp_pds: mcp_pds.to_owned(), profile_pds: profile.pds.clone(), }); } } } } }
for (name, service) in &config.services { for (role, named) in [ ("pds", service.pds.as_deref()), ("index", service.index.as_deref()), ("query", service.query.as_deref()), ] { let Some(named) = named else { continue }; if !config.services.contains_key(named) { problems.push(Problem::NoSuchUpstream { service: name.clone(), role, upstream: named.to_owned(), }); } } }
let mut by_listen: BTreeMap<String, Vec<String>> = BTreeMap::new(); for (name, service) in &config.services { by_listen .entry(service.listen()) .or_default() .push(name.clone()); } for (listen, services) in by_listen { if services.len() > 1 { problems.push(Problem::PortClash { listen, services }); } }
for binding in config.bindings() { if !config.profiles.contains_key(binding.profile) { problems.push(Problem::NoSuchProfile { dir: binding.dir.display().to_string(), profile: binding.profile.to_owned(), }); } }
if !config.profiles.contains_key(crate::DEFAULT_PROFILE) { problems.push(Problem::NoDefaultProfile); }
problems}
#[cfg(test)]mod tests { use super::*;
fn parse(raw: &str) -> Config { toml::from_str(raw).expect("parses") }
#[test] fn a_profile_naming_a_missing_service_is_reported() { let problems = parse( r#" [profile.default] pds = "nowhere" "#, ) .problems(); assert!( matches!( problems.as_slice(), [Problem::NoSuchService { service, .. }] if service == "nowhere" ), "{problems:?}" ); }
#[test] fn a_role_filled_by_the_wrong_kind_is_reported() { let problems = parse( r#" [service.pds-main] kind = "pds" port = 3000 [profile.default] pds = "pds-main" mcp = "pds-main" "#, ) .problems(); assert!( problems .iter() .any(|problem| matches!(problem, Problem::WrongKind { role: "mcp", .. })), "{problems:?}" ); }
#[test] fn a_scrobble_host_writing_somewhere_else_is_reported() { // The silent failure this exists to make loud: accounts minted on one // server, scrobbles written to another. let problems = parse( r#" [service.pds-a] kind = "pds" port = 3000 [service.pds-b] kind = "pds" port = 3100 [service.mcp-a] kind = "mcp" port = 3001 pds = "pds-b" [profile.default] pds = "pds-a" mcp = "mcp-a" "#, ) .problems(); assert!( problems .iter() .any(|problem| matches!(problem, Problem::Disagrees { .. })), "{problems:?}" ); }
#[test] fn two_services_on_one_port_are_reported() { let problems = parse( r#" [service.pds-a] kind = "pds" port = 3000 [service.pds-b] kind = "pds" port = 3000 [profile.default] pds = "pds-a" "#, ) .problems(); assert!( problems.iter().any(|problem| matches!( problem, Problem::PortClash { listen, .. } if listen == "127.0.0.1:3000" )), "{problems:?}" ); }
#[test] fn the_same_port_on_two_interfaces_is_not_a_clash() { let problems = parse( r#" [service.pds-a] kind = "pds" port = 3000 [service.pds-b] kind = "pds" port = 3000 host = "127.0.0.2" [profile.default] pds = "pds-a" "#, ) .problems(); assert!( !problems .iter() .any(|problem| matches!(problem, Problem::PortClash { .. })), "{problems:?}" ); }
#[test] fn a_binding_to_a_missing_profile_is_reported() { let problems = parse( r#" [service.pds-main] kind = "pds" port = 3000 [profile.default] pds = "pds-main" [bind] "/repo/a" = "ghost" "#, ) .problems(); assert!( problems .iter() .any(|problem| matches!(problem, Problem::NoSuchProfile { .. })), "{problems:?}" ); }
#[test] fn a_configuration_with_no_default_profile_says_so() { let problems = parse( r#" [service.pds-main] kind = "pds" port = 3000 [profile.beta] pds = "pds-main" "#, ) .problems(); assert!( problems.contains(&Problem::NoDefaultProfile), "{problems:?}" ); }
#[test] fn the_builtin_configuration_is_clean() { assert_eq!(Config::builtin().problems(), Problems::new()); }}