use std::io::Write;
use dre_protocol::delivery::Retry;
use dre_protocol::msg::ConnectionField;
use dre_protocol::msg::Message;
use dre_protocol::options::{OptionField, OptionType};
use dre_protocol::plugin::{
About, Delivery, Destination, Result, conn_bool, conn_required, serve_destination,
};
use dre_protocol::{CAP_MESSAGE, CAP_MESSAGE_ONLY, CAP_MULTI_FILE};
use serde_json::json;
struct Fixture;
impl Destination for Fixture {
fn connection_fields(&self) -> Vec<ConnectionField> {
vec![
ConnectionField::new("dir", "where deliveries are recorded").required(),
ConnectionField::new("temporary_failures", "how many tries fail with a temporary error"),
]
}
fn options(&self) -> Vec<OptionField> {
vec![
OptionField::new("to", OptionType::Strings, "recorded"),
OptionField::new("subject", OptionType::String, "recorded"),
OptionField::new("message", OptionType::String, "recorded"),
]
}
fn message_limit(&self) -> Option<u64> {
Some(3_000)
}
fn deliver_message(&mut self, d: &Delivery, m: &Message) -> Result<String> {
let names = record(d, Some(m))?;
Ok(match names.is_empty() {
true => "fixture:message".to_string(),
false => format!("fixture:message+{}", names.join(",")),
})
}
fn deliver_files(&mut self, d: &Delivery) -> Result<String> {
if std::env::var_os("DRE_FIXTURE_MESSAGE_ONLY").is_some() {
return Err("the fixture destination only takes messages".into());
}
let failures = d
.connection
.get("temporary_failures")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let mut tries = 0;
dre_protocol::delivery::retry(3, "fixture", || {
tries += 1;
if tries <= failures {
return Err(Retry::Temporary(
"temporary failure".to_string(),
Some(std::time::Duration::ZERO),
));
}
Ok(())
})?;
let names = record(d, None)?;
Ok(format!("fixture:{}", names.join(",")))
}
}
fn record(d: &Delivery, message: Option<&Message>) -> Result<Vec<String>> {
if conn_bool(&d.connection, "fail") == Some(true) {
return Err("fixture destination told to fail".into());
}
let dir = conn_required(&d.connection, "dir")?;
let mut files = Vec::new();
let mut names = Vec::new();
for f in &d.files {
let content = std::fs::read(&f.local)
.map(|b| String::from_utf8_lossy(&b).into_owned())
.map_err(|e| format!("can't read {}: {e}", f.local.display()))?;
names.push(f.remote.clone().unwrap_or_else(|| {
f.local
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
}));
files.push(json!({"local": f.local, "remote": f.remote, "content": content}));
}
std::fs::create_dir_all(dir)?;
let mut log = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(std::path::Path::new(dir).join("deliveries.jsonl"))?;
let mut line = json!({"files": files, "options": d.options});
if let Some(m) = message {
line["message"] = json!(m);
}
writeln!(log, "{line}")?;
Ok(names)
}
fn main() {
let on = |v: &str| std::env::var_os(v).is_some();
let mut caps: Vec<&'static str> = Vec::new();
if !on("DRE_FIXTURE_SINGLE_FILE") {
caps.push(CAP_MULTI_FILE);
}
if !on("DRE_FIXTURE_FILES_ONLY") {
caps.push(CAP_MESSAGE);
}
if on("DRE_FIXTURE_MESSAGE_ONLY") {
caps.push(CAP_MESSAGE_ONLY);
}
let caps: &'static [&'static str] = caps.leak();
serve_destination(
About::new("fixture", env!("CARGO_PKG_VERSION")).capabilities(caps),
Fixture,
)
}