1use crate::rfc5965::ARFReport;
2use bounce_classify::BounceClass;
3use chrono::{DateTime, Utc};
4use kumo_address::host::HostAddress;
5use kumo_address::socket::SocketAddress;
6use rfc5321::Response;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::borrow::Cow;
10use std::collections::HashMap;
11use std::net::SocketAddr;
12use uuid::Uuid;
13
14pub mod rfc3464;
15pub mod rfc5965;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ResolvedAddress {
19 pub name: String,
20 pub addr: HostAddress,
21}
22
23impl std::fmt::Display for ResolvedAddress {
24 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
25 let addr = format!("{}", self.addr);
26 if addr == self.name {
27 write!(fmt, "{addr}")
29 } else {
30 write!(fmt, "{}/{addr}", self.name)
31 }
32 }
33}
34
35#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
36pub enum RecordType {
37 Reception,
39 Delivery,
42 Bounce,
43 TransientFailure,
44 Expiration,
46 AdminBounce,
48 OOB,
50 Feedback,
52
53 Rejection,
55
56 AdminRebind,
58
59 DeferredInjectionRebind,
62
63 Delayed,
65
66 Any,
68}
69
70impl RecordType {
71 pub const fn is_bounce_classifiable(&self) -> bool {
77 match self {
78 Self::Any
79 | Self::Reception
80 | Self::Delivery
81 | Self::DeferredInjectionRebind
82 | Self::AdminRebind
83 | Self::Delayed => false,
84 Self::Bounce
85 | Self::TransientFailure
86 | Self::Expiration
87 | Self::AdminBounce
88 | Self::OOB
89 | Self::Feedback
90 | Self::Rejection => true,
91 }
92 }
93}
94
95#[derive(Serialize, Deserialize, Debug, Clone)]
96pub struct JsonLogRecord {
97 #[serde(rename = "type")]
99 pub kind: RecordType,
100 pub id: String,
102 pub sender: String,
104 pub recipient: String,
106 pub queue: String,
108 pub site: String,
110 pub size: u64,
112 pub response: Response,
114 pub peer_address: Option<ResolvedAddress>,
117 #[serde(with = "chrono::serde::ts_seconds")]
119 pub timestamp: DateTime<Utc>,
120 #[serde(with = "chrono::serde::ts_seconds")]
122 pub created: DateTime<Utc>,
123 pub num_attempts: u16,
127
128 pub bounce_classification: BounceClass,
129
130 pub egress_pool: Option<String>,
131 pub egress_source: Option<String>,
132 pub source_address: Option<MaybeProxiedSourceAddress>,
133
134 pub feedback_report: Option<Box<ARFReport>>,
135
136 pub meta: HashMap<String, Value>,
137 pub headers: HashMap<String, Value>,
138
139 pub delivery_protocol: Option<String>,
141
142 pub reception_protocol: Option<String>,
144
145 pub nodeid: Uuid,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub tls_cipher: Option<String>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub tls_protocol_version: Option<String>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub tls_peer_subject_name: Option<Vec<String>>,
159
160 #[serde(skip_serializing_if = "Option::is_none")]
164 pub provider_name: Option<String>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
171 pub session_id: Option<Uuid>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct MaybeProxiedSourceAddress {
176 pub address: SocketAddress,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub server: Option<SocketAddr>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub protocol: Option<Cow<'static, str>>,
181}
182
183#[cfg(all(test, target_pointer_width = "64"))]
184#[test]
185fn sizes() {
186 assert_eq!(std::mem::size_of::<JsonLogRecord>(), 712);
187}