use base64::engine::general_purpose::STANDARD as B64;
use base64::Engine;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
pub const RECORDING_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(rename_all = "camelCase")]
pub struct Recording {
pub version: u32,
pub exported_at: i64,
#[serde(default)]
pub label: Option<String>,
pub messages: Vec<RecordedMessage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(rename_all = "camelCase")]
pub struct RecordedMessage {
pub envelope: RecordedEnvelope,
pub raw_b64: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[serde(rename_all = "camelCase")]
pub struct RecordedEnvelope {
pub mail_from: String,
pub rcpt_to: Vec<String>,
pub received_at: i64,
pub ext_smtputf8: bool,
pub ext_8bitmime: bool,
pub subject: Option<String>,
}
impl Recording {
pub fn new(label: Option<String>) -> Self {
Self {
version: RECORDING_VERSION,
exported_at: chrono::Utc::now().timestamp_millis(),
label,
messages: Vec::new(),
}
}
pub fn validate(&self) -> Result<()> {
if self.version != RECORDING_VERSION {
return Err(Error::Invalid(format!(
"unsupported recording version {} (expected {})",
self.version, RECORDING_VERSION
)));
}
Ok(())
}
}
pub fn decode_raw(msg: &RecordedMessage) -> Result<Vec<u8>> {
B64.decode(&msg.raw_b64)
.map_err(|e| Error::Invalid(format!("base64 decode: {e}")))
}
pub fn encode_raw(raw: &[u8]) -> String {
B64.encode(raw)
}