postcrate_core/
recording.rs1use base64::engine::general_purpose::STANDARD as B64;
18use base64::Engine;
19use serde::{Deserialize, Serialize};
20
21use crate::error::{Error, Result};
22
23pub const RECORDING_VERSION: u32 = 1;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[cfg_attr(feature = "specta", derive(specta::Type))]
27#[serde(rename_all = "camelCase")]
28pub struct Recording {
29 pub version: u32,
30 pub exported_at: i64,
31 #[serde(default)]
33 pub label: Option<String>,
34 pub messages: Vec<RecordedMessage>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[cfg_attr(feature = "specta", derive(specta::Type))]
39#[serde(rename_all = "camelCase")]
40pub struct RecordedMessage {
41 pub envelope: RecordedEnvelope,
42 pub raw_b64: String,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[cfg_attr(feature = "specta", derive(specta::Type))]
49#[serde(rename_all = "camelCase")]
50pub struct RecordedEnvelope {
51 pub mail_from: String,
52 pub rcpt_to: Vec<String>,
53 pub received_at: i64,
54 pub ext_smtputf8: bool,
55 pub ext_8bitmime: bool,
56 pub subject: Option<String>,
58}
59
60impl Recording {
61 pub fn new(label: Option<String>) -> Self {
62 Self {
63 version: RECORDING_VERSION,
64 exported_at: chrono::Utc::now().timestamp_millis(),
65 label,
66 messages: Vec::new(),
67 }
68 }
69
70 pub fn validate(&self) -> Result<()> {
73 if self.version != RECORDING_VERSION {
74 return Err(Error::Invalid(format!(
75 "unsupported recording version {} (expected {})",
76 self.version, RECORDING_VERSION
77 )));
78 }
79 Ok(())
80 }
81}
82
83pub fn decode_raw(msg: &RecordedMessage) -> Result<Vec<u8>> {
85 B64.decode(&msg.raw_b64)
86 .map_err(|e| Error::Invalid(format!("base64 decode: {e}")))
87}
88
89pub fn encode_raw(raw: &[u8]) -> String {
91 B64.encode(raw)
92}