1use hashbrown::HashMap;
16use jiff::Timestamp;
17use rustfs_s3_types::EventName;
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
23pub struct ObjectVersion {
24 #[serde(rename = "objectName")]
25 pub object_name: String,
26 #[serde(rename = "versionId", skip_serializing_if = "Option::is_none")]
27 pub version_id: Option<String>,
28}
29
30impl ObjectVersion {
31 pub fn new(object_name: String, version_id: Option<String>) -> Self {
32 Self { object_name, version_id }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, Default)]
38pub struct ApiDetails {
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub name: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub bucket: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub object: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub objects: Option<Vec<ObjectVersion>>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub status: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub status_code: Option<i32>,
51 #[serde(rename = "rx", skip_serializing_if = "Option::is_none")]
52 pub input_bytes: Option<i64>,
53 #[serde(rename = "tx", skip_serializing_if = "Option::is_none")]
54 pub output_bytes: Option<i64>,
55 #[serde(rename = "txHeaders", skip_serializing_if = "Option::is_none")]
56 pub header_bytes: Option<i64>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub time_to_first_byte: Option<String>,
59 #[serde(rename = "timeToFirstByteInNS", skip_serializing_if = "Option::is_none")]
60 pub time_to_first_byte_in_ns: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub time_to_response: Option<String>,
63 #[serde(rename = "timeToResponseInNS", skip_serializing_if = "Option::is_none")]
64 pub time_to_response_in_ns: Option<String>,
65}
66
67#[derive(Default, Clone)]
69pub struct ApiDetailsBuilder(pub ApiDetails);
70
71impl ApiDetailsBuilder {
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn name(mut self, name: impl Into<String>) -> Self {
77 self.0.name = Some(name.into());
78 self
79 }
80
81 pub fn bucket(mut self, bucket: impl Into<String>) -> Self {
82 self.0.bucket = Some(bucket.into());
83 self
84 }
85
86 pub fn object(mut self, object: impl Into<String>) -> Self {
87 self.0.object = Some(object.into());
88 self
89 }
90
91 pub fn objects(mut self, objects: Vec<ObjectVersion>) -> Self {
92 self.0.objects = Some(objects);
93 self
94 }
95
96 pub fn status(mut self, status: impl Into<String>) -> Self {
97 self.0.status = Some(status.into());
98 self
99 }
100
101 pub fn status_code(mut self, code: i32) -> Self {
102 self.0.status_code = Some(code);
103 self
104 }
105
106 pub fn input_bytes(mut self, bytes: i64) -> Self {
107 self.0.input_bytes = Some(bytes);
108 self
109 }
110
111 pub fn output_bytes(mut self, bytes: i64) -> Self {
112 self.0.output_bytes = Some(bytes);
113 self
114 }
115
116 pub fn header_bytes(mut self, bytes: i64) -> Self {
117 self.0.header_bytes = Some(bytes);
118 self
119 }
120
121 pub fn time_to_first_byte(mut self, t: impl Into<String>) -> Self {
122 self.0.time_to_first_byte = Some(t.into());
123 self
124 }
125
126 pub fn time_to_first_byte_in_ns(mut self, t: impl Into<String>) -> Self {
127 self.0.time_to_first_byte_in_ns = Some(t.into());
128 self
129 }
130
131 pub fn time_to_response(mut self, t: impl Into<String>) -> Self {
132 self.0.time_to_response = Some(t.into());
133 self
134 }
135
136 pub fn time_to_response_in_ns(mut self, t: impl Into<String>) -> Self {
137 self.0.time_to_response_in_ns = Some(t.into());
138 self
139 }
140
141 pub fn build(self) -> ApiDetails {
142 self.0
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, Default)]
148pub struct AuditEntry {
149 pub version: String,
150 #[serde(rename = "deploymentid", skip_serializing_if = "Option::is_none")]
151 pub deployment_id: Option<String>,
152 #[serde(rename = "siteName", skip_serializing_if = "Option::is_none")]
153 pub site_name: Option<String>,
154 #[serde(with = "jiff::fmt::serde::timestamp::millisecond::required")]
155 pub time: Timestamp,
156 pub event: EventName,
157 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
158 pub entry_type: Option<String>,
159 pub trigger: String,
160 pub api: ApiDetails,
161 #[serde(rename = "remotehost", skip_serializing_if = "Option::is_none")]
162 pub remote_host: Option<String>,
163 #[serde(rename = "requestID", skip_serializing_if = "Option::is_none")]
166 pub request_id: Option<String>,
167 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
168 pub user_agent: Option<String>,
169 #[serde(rename = "requestPath", skip_serializing_if = "Option::is_none")]
170 pub req_path: Option<String>,
171 #[serde(rename = "requestHost", skip_serializing_if = "Option::is_none")]
172 pub req_host: Option<String>,
173 #[serde(rename = "requestNode", skip_serializing_if = "Option::is_none")]
174 pub req_node: Option<String>,
175 #[serde(rename = "requestClaims", skip_serializing_if = "Option::is_none")]
176 pub req_claims: Option<HashMap<String, Value>>,
177 #[serde(rename = "requestQuery", skip_serializing_if = "Option::is_none")]
178 pub req_query: Option<HashMap<String, String>>,
179 #[serde(rename = "requestHeader", skip_serializing_if = "Option::is_none")]
180 pub req_header: Option<HashMap<String, String>>,
181 #[serde(rename = "responseHeader", skip_serializing_if = "Option::is_none")]
182 pub resp_header: Option<HashMap<String, String>>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub tags: Option<HashMap<String, Value>>,
185 #[serde(rename = "accessKey", skip_serializing_if = "Option::is_none")]
186 pub access_key: Option<String>,
187 #[serde(rename = "parentUser", skip_serializing_if = "Option::is_none")]
188 pub parent_user: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub error: Option<String>,
191}
192
193pub struct AuditEntryBuilder(AuditEntry);
195
196impl AuditEntryBuilder {
197 pub fn new(version: impl Into<String>, event: EventName, trigger: impl Into<String>, api: ApiDetails) -> Self {
199 Self(AuditEntry {
200 version: version.into(),
201 time: Timestamp::now(),
202 event,
203 trigger: trigger.into(),
204 api,
205 ..Default::default()
206 })
207 }
208
209 pub fn version(mut self, version: impl Into<String>) -> Self {
211 self.0.version = version.into();
212 self
213 }
214
215 pub fn event(mut self, event: EventName) -> Self {
216 self.0.event = event;
217 self
218 }
219
220 pub fn api(mut self, api_details: ApiDetails) -> Self {
221 self.0.api = api_details;
222 self
223 }
224
225 pub fn deployment_id(mut self, id: impl Into<String>) -> Self {
226 self.0.deployment_id = Some(id.into());
227 self
228 }
229
230 pub fn site_name(mut self, name: impl Into<String>) -> Self {
231 self.0.site_name = Some(name.into());
232 self
233 }
234
235 pub fn time(mut self, time: Timestamp) -> Self {
236 self.0.time = time;
237 self
238 }
239
240 pub fn entry_type(mut self, entry_type: impl Into<String>) -> Self {
241 self.0.entry_type = Some(entry_type.into());
242 self
243 }
244
245 pub fn remote_host(mut self, host: impl Into<String>) -> Self {
246 self.0.remote_host = Some(host.into());
247 self
248 }
249
250 pub fn request_id(mut self, id: impl Into<String>) -> Self {
251 self.0.request_id = Some(id.into());
252 self
253 }
254
255 pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
256 self.0.user_agent = Some(agent.into());
257 self
258 }
259
260 pub fn req_path(mut self, path: impl Into<String>) -> Self {
261 self.0.req_path = Some(path.into());
262 self
263 }
264
265 pub fn req_host(mut self, host: impl Into<String>) -> Self {
266 self.0.req_host = Some(host.into());
267 self
268 }
269
270 pub fn req_node(mut self, node: impl Into<String>) -> Self {
271 self.0.req_node = Some(node.into());
272 self
273 }
274
275 pub fn req_claims(mut self, claims: HashMap<String, Value>) -> Self {
276 self.0.req_claims = Some(claims);
277 self
278 }
279
280 pub fn req_query(mut self, query: HashMap<String, String>) -> Self {
281 self.0.req_query = Some(query);
282 self
283 }
284
285 pub fn req_header(mut self, header: HashMap<String, String>) -> Self {
286 self.0.req_header = Some(header);
287 self
288 }
289
290 pub fn resp_header(mut self, header: HashMap<String, String>) -> Self {
291 self.0.resp_header = Some(header);
292 self
293 }
294
295 pub fn tags(mut self, tags: HashMap<String, Value>) -> Self {
296 self.0.tags = Some(tags);
297 self
298 }
299
300 pub fn access_key(mut self, key: impl Into<String>) -> Self {
301 self.0.access_key = Some(key.into());
302 self
303 }
304
305 pub fn parent_user(mut self, user: impl Into<String>) -> Self {
306 self.0.parent_user = Some(user.into());
307 self
308 }
309
310 pub fn error(mut self, error: impl Into<String>) -> Self {
311 self.0.error = Some(error.into());
312 self
313 }
314
315 pub fn build(self) -> AuditEntry {
317 self.0
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use serde_json::Value;
325
326 #[test]
327 fn audit_entry_serializes_historical_request_id_field_name() {
328 let entry = AuditEntryBuilder::new(
329 "1",
330 EventName::ObjectCreatedPut,
331 "s3",
332 ApiDetailsBuilder::new()
333 .name("PutObject")
334 .status("OK")
335 .status_code(200)
336 .build(),
337 )
338 .request_id("req-audit-123")
339 .build();
340
341 let value = serde_json::to_value(entry).expect("audit entry should serialize");
342 assert_eq!(value["requestID"], Value::String("req-audit-123".to_string()));
343 assert!(value.get("request_id").is_none(), "historical audit contract must not expose request_id");
344 }
345
346 #[test]
347 fn audit_entry_time_serializes_as_epoch_milliseconds() {
348 let entry = AuditEntryBuilder::new(
349 "1",
350 EventName::ObjectCreatedPut,
351 "s3",
352 ApiDetailsBuilder::new()
353 .name("PutObject")
354 .status("OK")
355 .status_code(200)
356 .build(),
357 )
358 .time(Timestamp::from_millisecond(1_711_423_698_870).expect("timestamp should be valid"))
359 .build();
360
361 let value = serde_json::to_value(entry).expect("audit entry should serialize");
362 assert_eq!(value["time"], Value::Number(1_711_423_698_870_i64.into()));
363 }
364}