refactor: define caps from one token=>field list via a macro that generates SUPPORTED_CAPS + the Caps struct + has/set — an advertised-but-unwired cap (or an unwired field) no longer compiles

This commit is contained in:
Jean Chevronnet 2026-08-18 22:29:47 +00:00
parent a3dc7b521f
commit 30754f08b5

View file

@ -103,76 +103,73 @@ impl UserFlags {
} }
} }
/// The IRCv3 capabilities advertised. Order = the CAP LS order. /// Declare every advertised capability exactly once — the CAP LS token and the
pub const SUPPORTED_CAPS: &[&str] = &[ /// `Caps` bool field it toggles — and derive `SUPPORTED_CAPS`, the `Caps` struct,
"sasl", /// and `has`/`set` from that single list. This makes "advertised but not wired up"
"server-time", /// (or a field with no token) a compile error rather than a silent bug.
"message-tags", macro_rules! define_caps {
"multi-prefix", ($($tok:literal => $field:ident),+ $(,)?) => {
"away-notify", /// The IRCv3 capabilities advertised, in CAP LS order.
"account-notify", pub const SUPPORTED_CAPS: &[&str] = &[$($tok),+];
"extended-join",
"chghost",
"userhost-in-names",
"echo-message",
"invite-notify",
"setname",
"extended-monitor",
"account-tag",
"standard-replies",
"labeled-response",
"batch",
"draft/chathistory",
"draft/message-redaction",
"draft/pre-away",
"draft/metadata-2",
"draft/multiline",
"draft/account-registration",
"draft/json-log",
"draft/extended-isupport",
"reverse.im/filehost",
"draft/relaymsg",
"draft/channel-rename",
"draft/read-marker",
"no-implicit-names",
"cap-notify",
];
/// Per-connection IRCv3 capability state, one flat set. Toggled by `CAP REQ`; /// Per-connection IRCv3 capability state, one bool per advertised cap;
/// consulted wherever a line is formatted per-client. /// toggled by `CAP REQ`, consulted wherever a line is formatted per-client.
#[derive(Default)] #[derive(Default)]
pub struct Caps { pub struct Caps {
pub sasl: bool, $(pub $field: bool,)+
pub server_time: bool, }
pub message_tags: bool,
pub multi_prefix: bool, impl Caps {
pub away_notify: bool, /// Whether `name` is currently enabled on this connection.
pub account_notify: bool, pub fn has(&self, name: &str) -> bool {
pub extended_join: bool, match name {
pub chghost: bool, $($tok => self.$field,)+
pub userhost_in_names: bool, _ => false,
pub echo_message: bool, }
pub invite_notify: bool, }
pub setname: bool, /// Enable/disable a cap by name; returns whether the name was recognised.
pub extended_monitor: bool, // route away/account/chghost/setname for MONITOR targets pub fn set(&mut self, name: &str, on: bool) -> bool {
pub account_tag: bool, // prepend account=<name> tag on messages from logged-in users match name {
pub standard_replies: bool, // understands FAIL/WARN/NOTE structured replies $($tok => { self.$field = on; true })+
pub labeled_response: bool, // tag responses to a labeled command with its label _ => false,
pub batch: bool, // understands BATCH framing }
pub chathistory: bool, // draft/chathistory — can request message history }
pub message_redaction: bool, // draft/message-redaction — understands REDACT }
pub pre_away: bool, // draft/pre-away — may set AWAY before registration };
pub metadata: bool, // draft/metadata-2 — wants metadata + change notices }
pub multiline: bool, // draft/multiline — may send multiline message batches
pub acct_registration: bool, // draft/account-registration — REGISTER/VERIFY understood define_caps! {
pub json_log: bool, // draft/json-log — structured JSON tag on server notices "sasl" => sasl,
pub ext_isupport: bool, // draft/extended-isupport — ISUPPORT command + batched 005 "server-time" => server_time,
pub filehost: bool, // reverse.im/filehost — knows the file-host extension "message-tags" => message_tags,
pub relaymsg: bool, // draft/relaymsg — may use RELAYMSG (bridge relaying) "multi-prefix" => multi_prefix,
pub channel_rename: bool, // draft/channel-rename — receives RENAME (else PART+JOIN) "away-notify" => away_notify,
pub read_marker: bool, // draft/read-marker — MARKREAD sync across the identity "account-notify" => account_notify,
pub no_implicit_names: bool, // no-implicit-names — suppress the auto NAMES after JOIN "extended-join" => extended_join,
pub cap_notify: bool, "chghost" => chghost,
"userhost-in-names" => userhost_in_names,
"echo-message" => echo_message,
"invite-notify" => invite_notify,
"setname" => setname,
"extended-monitor" => extended_monitor,
"account-tag" => account_tag,
"standard-replies" => standard_replies,
"labeled-response" => labeled_response,
"batch" => batch,
"draft/chathistory" => chathistory,
"draft/message-redaction" => message_redaction,
"draft/pre-away" => pre_away,
"draft/metadata-2" => metadata,
"draft/multiline" => multiline,
"draft/account-registration" => acct_registration,
"draft/json-log" => json_log,
"draft/extended-isupport" => ext_isupport,
"reverse.im/filehost" => filehost,
"draft/relaymsg" => relaymsg,
"draft/channel-rename" => channel_rename,
"draft/read-marker" => read_marker,
"no-implicit-names" => no_implicit_names,
"cap-notify" => cap_notify,
} }
impl Caps { impl Caps {
@ -211,83 +208,6 @@ impl Caps {
.join(" ") .join(" ")
} }
pub fn has(&self, name: &str) -> bool {
match name {
"sasl" => self.sasl,
"server-time" => self.server_time,
"message-tags" => self.message_tags,
"multi-prefix" => self.multi_prefix,
"away-notify" => self.away_notify,
"account-notify" => self.account_notify,
"extended-join" => self.extended_join,
"chghost" => self.chghost,
"userhost-in-names" => self.userhost_in_names,
"echo-message" => self.echo_message,
"invite-notify" => self.invite_notify,
"setname" => self.setname,
"extended-monitor" => self.extended_monitor,
"account-tag" => self.account_tag,
"standard-replies" => self.standard_replies,
"labeled-response" => self.labeled_response,
"batch" => self.batch,
"draft/chathistory" => self.chathistory,
"draft/message-redaction" => self.message_redaction,
"draft/pre-away" => self.pre_away,
"draft/metadata-2" => self.metadata,
"draft/multiline" => self.multiline,
"draft/account-registration" => self.acct_registration,
"draft/json-log" => self.json_log,
"draft/extended-isupport" => self.ext_isupport,
"reverse.im/filehost" => self.filehost,
"draft/relaymsg" => self.relaymsg,
"draft/channel-rename" => self.channel_rename,
"draft/read-marker" => self.read_marker,
"no-implicit-names" => self.no_implicit_names,
"cap-notify" => self.cap_notify,
_ => false,
}
}
/// Enable/disable a cap by name; returns whether the name was recognised.
pub fn set(&mut self, name: &str, on: bool) -> bool {
let field = match name {
"sasl" => &mut self.sasl,
"server-time" => &mut self.server_time,
"message-tags" => &mut self.message_tags,
"multi-prefix" => &mut self.multi_prefix,
"away-notify" => &mut self.away_notify,
"account-notify" => &mut self.account_notify,
"extended-join" => &mut self.extended_join,
"chghost" => &mut self.chghost,
"userhost-in-names" => &mut self.userhost_in_names,
"echo-message" => &mut self.echo_message,
"invite-notify" => &mut self.invite_notify,
"setname" => &mut self.setname,
"extended-monitor" => &mut self.extended_monitor,
"account-tag" => &mut self.account_tag,
"standard-replies" => &mut self.standard_replies,
"labeled-response" => &mut self.labeled_response,
"batch" => &mut self.batch,
"draft/chathistory" => &mut self.chathistory,
"draft/message-redaction" => &mut self.message_redaction,
"draft/pre-away" => &mut self.pre_away,
"draft/metadata-2" => &mut self.metadata,
"draft/multiline" => &mut self.multiline,
"draft/account-registration" => &mut self.acct_registration,
"draft/json-log" => &mut self.json_log,
"draft/extended-isupport" => &mut self.ext_isupport,
"reverse.im/filehost" => &mut self.filehost,
"draft/relaymsg" => &mut self.relaymsg,
"draft/channel-rename" => &mut self.channel_rename,
"draft/read-marker" => &mut self.read_marker,
"no-implicit-names" => &mut self.no_implicit_names,
"cap-notify" => &mut self.cap_notify,
_ => return false,
};
*field = on;
true
}
/// Space-separated list of the currently-enabled caps (for `CAP LIST`). /// Space-separated list of the currently-enabled caps (for `CAP LIST`).
pub fn enabled(&self) -> String { pub fn enabled(&self) -> String {
SUPPORTED_CAPS SUPPORTED_CAPS