From 0ed668452214cbe2d730ad735c3c976b0eab6a79 Mon Sep 17 00:00:00 2001 From: Jean Date: Tue, 14 Jul 2026 23:31:44 +0000 Subject: [PATCH] Validate config on load; document the SDK's core traits Config::load now checks the SID is 3 alphanumeric chars and that server.name and uplink.host are set, failing with a clear message instead of a cryptic link-time error. Give the Service and Protocol traits and ServiceCtx proper rustdoc so cargo doc is useful to module authors. --- api/src/lib.rs | 15 ++++++++++----- src/config.rs | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index d9d5087..c68d134 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -121,9 +121,9 @@ pub enum RegReply { NickServ { agent: String, uid: String, nick: String }, } -// The ircd link layer. The engine only ever sees NetEvent / NetAction; raw -// server-to-server lines live entirely behind a Protocol impl, so a new ircd is -// one new module and the engine is untouched. +/// The ircd link layer. The engine only ever sees [`NetEvent`] / [`NetAction`]; +/// raw server-to-server lines live entirely behind a `Protocol` impl, so a new +/// ircd is one new module and the engine is untouched. pub trait Protocol: Send { /// Lines to send immediately on connect (auth / capability negotiation). fn handshake(&mut self) -> Vec; @@ -226,8 +226,9 @@ pub struct Sender<'a> { pub privs: Privs, } -// The intent sink a service writes to. A service never mutates the network or -// the store itself; it pushes normalized actions the engine drains and applies. +/// The intent sink a service writes to. A service never mutates the network or +/// the store itself; it pushes normalized actions (via [`ServiceCtx::notice`] +/// and friends) that the engine drains and applies. #[derive(Default)] pub struct ServiceCtx { pub actions: Vec, @@ -1075,6 +1076,10 @@ pub struct IncidentView { // A pseudo-client (NickServ, ChanServ, ...). Introduced at burst, receives the // commands users message it, reads/writes the store, and pushes actions. +/// A pseudo-client such as NickServ or ChanServ. Implement this, register the +/// crate in the daemon, and the engine routes a PRIVMSG addressed to the +/// service into [`Service::on_command`]. A service touches the network and the +/// store only through [`NetView`] and [`Store`]. pub trait Service: Send { fn nick(&self) -> &str; fn uid(&self) -> &str; diff --git a/src/config.rs b/src/config.rs index a425aab..6442ba7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -278,6 +278,24 @@ fn default_scram_iterations() -> u32 { impl Config { pub fn load(path: &str) -> anyhow::Result { let raw = std::fs::read_to_string(path)?; - Ok(toml::from_str(&raw)?) + let cfg: Config = toml::from_str(&raw)?; + cfg.validate()?; + Ok(cfg) + } + + // Catch the common misconfigurations up front with a clear message, rather + // than failing cryptically at link time. + fn validate(&self) -> anyhow::Result<()> { + let sid = &self.server.sid; + if sid.len() != 3 || !sid.chars().all(|c| c.is_ascii_alphanumeric()) { + anyhow::bail!("server.sid must be exactly 3 alphanumeric characters (got {sid:?})"); + } + if self.server.name.trim().is_empty() { + anyhow::bail!("server.name must not be empty"); + } + if self.uplink.host.trim().is_empty() { + anyhow::bail!("uplink.host must not be empty"); + } + Ok(()) } }