refactor: reject mail with a reason identifier, not a sentence
dispatch and apply_commands returned English prose that became the reply subject, so the pure layers owned author-facing copy. They now return reason::Reason and the mail-building edge resolves it through the catalogue. core::rules needed no change: RuleError was already a typed enum, and its Display stays for author-side tools. The daemon maps the variants itself rather than borrowing that prose. Error-path tests now assert on variants, so rewording main.ftl cannot break them, and PruneModeUnknown carries the value the author actually sent. Reason::every_variant_resolves_to_copy walks the enum, so a variant added without a catalogue entry fails in tests rather than mailing someone "???reason-…???".
This commit is contained in:
parent
3279fd3a2d
commit
bf6e2eaa15
5 changed files with 244 additions and 42 deletions
|
|
@ -13,6 +13,7 @@ use smolboid_core::envelope;
|
|||
use crate::config::Config;
|
||||
use crate::dispatch::{self, Action, Command, Incoming};
|
||||
use crate::feedstore::FeedStore;
|
||||
use crate::reason::Reason;
|
||||
use crate::state::{payload_key, reply_allowed, FeedState, PruneMode, State};
|
||||
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
|
@ -131,7 +132,7 @@ pub fn apply_commands(
|
|||
feed: &mut Feed,
|
||||
fs: &mut FeedState,
|
||||
commands: &[Command],
|
||||
) -> Result<usize, String> {
|
||||
) -> Result<usize, Reason> {
|
||||
for command in commands {
|
||||
match command {
|
||||
Command::Nick(nick) => {
|
||||
|
|
@ -140,7 +141,7 @@ pub fn apply_commands(
|
|||
}
|
||||
Command::Delete(timestamp) => {
|
||||
if !feed.delete(timestamp) {
|
||||
return Err(format!("no record at {timestamp}"));
|
||||
return Err(Reason::NoRecordAt { timestamp: timestamp.clone() });
|
||||
}
|
||||
}
|
||||
Command::Prune(mode) => fs.prune = *mode,
|
||||
|
|
@ -368,20 +369,13 @@ impl Daemon {
|
|||
Ok(true)
|
||||
}
|
||||
Err(e) => {
|
||||
let reason = match &e {
|
||||
ApplyError::OverQuota(repost) => format!(
|
||||
"daily {} quota exceeded; try tomorrow",
|
||||
if *repost { "repost" } else { "post" }
|
||||
),
|
||||
ApplyError::Collision(ts) => {
|
||||
format!("timestamp {ts} already exists; wait a second and resend")
|
||||
}
|
||||
ApplyError::FeedFull => "feed is at its size cap; delete records with Twt-Delete or choose Twt-Prune: fifo".to_owned(),
|
||||
ApplyError::ReplyToChanged => format!(
|
||||
"your Reply-To does not match the feed's bound author {}; \
|
||||
send from that address or mail the operator",
|
||||
feed.author().unwrap_or_default()
|
||||
),
|
||||
let reason = match e {
|
||||
ApplyError::OverQuota(repost) => Reason::OverQuota { repost },
|
||||
ApplyError::Collision(timestamp) => Reason::Collision { timestamp },
|
||||
ApplyError::FeedFull => Reason::FeedFull,
|
||||
ApplyError::ReplyToChanged => Reason::ReplyToChanged {
|
||||
author: feed.author().unwrap_or_default().to_owned(),
|
||||
},
|
||||
};
|
||||
self.queue_reply(state, incoming, address, &reason, replies);
|
||||
Ok(false)
|
||||
|
|
@ -395,7 +389,7 @@ impl Daemon {
|
|||
state: &mut MutexGuard<'_, State>,
|
||||
incoming: &Incoming,
|
||||
address: &str,
|
||||
reason: &str,
|
||||
reason: &Reason,
|
||||
replies: &mut Vec<(String, String, String)>,
|
||||
) {
|
||||
let key = payload_key(&incoming.sender, incoming.time, &incoming.body);
|
||||
|
|
@ -405,12 +399,13 @@ impl Daemon {
|
|||
let text = std::str::from_utf8(&incoming.body)
|
||||
.map(|t| t.chars().take(200).collect::<String>())
|
||||
.unwrap_or_default();
|
||||
let sentence = reason.text();
|
||||
let mut args = fluent_bundle::FluentArgs::new();
|
||||
args.set("reason", reason.to_owned());
|
||||
args.set("reason", sentence.clone());
|
||||
args.set("quoted", text);
|
||||
replies.push((
|
||||
address.to_owned(),
|
||||
crate::i18n::t1("reply-subject", "reason", reason.to_owned()),
|
||||
crate::i18n::t1("reply-subject", "reason", sentence),
|
||||
crate::i18n::t_args("reply-body", Some(&args)),
|
||||
));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue