1use chrono::{DateTime, Utc};
7use pallas_codec::minicbor::{Decode, Encode};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15#[repr(u8)]
16pub enum Severity {
17 Debug = 0,
19 Info = 1,
21 Notice = 2,
23 Warning = 3,
25 Error = 4,
27 Critical = 5,
29 Alert = 6,
31 Emergency = 7,
33}
34
35impl Encode<()> for Severity {
36 fn encode<W: pallas_codec::minicbor::encode::Write>(
37 &self,
38 e: &mut pallas_codec::minicbor::Encoder<W>,
39 _ctx: &mut (),
40 ) -> Result<(), pallas_codec::minicbor::encode::Error<W::Error>> {
41 e.array(1)?.u8(*self as u8)?;
43 Ok(())
44 }
45}
46
47impl<'b> Decode<'b, ()> for Severity {
48 fn decode(
49 d: &mut pallas_codec::minicbor::Decoder<'b>,
50 _ctx: &mut (),
51 ) -> Result<Self, pallas_codec::minicbor::decode::Error> {
52 d.array()?;
53 let val = d.u8()?;
54 match val {
55 0 => Ok(Severity::Debug),
56 1 => Ok(Severity::Info),
57 2 => Ok(Severity::Notice),
58 3 => Ok(Severity::Warning),
59 4 => Ok(Severity::Error),
60 5 => Ok(Severity::Critical),
61 6 => Ok(Severity::Alert),
62 7 => Ok(Severity::Emergency),
63 _ => Err(pallas_codec::minicbor::decode::Error::message(
64 "invalid severity value",
65 )),
66 }
67 }
68}
69
70impl fmt::Display for Severity {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Severity::Debug => write!(f, "Debug"),
74 Severity::Info => write!(f, "Info"),
75 Severity::Notice => write!(f, "Notice"),
76 Severity::Warning => write!(f, "Warning"),
77 Severity::Error => write!(f, "Error"),
78 Severity::Critical => write!(f, "Critical"),
79 Severity::Alert => write!(f, "Alert"),
80 Severity::Emergency => write!(f, "Emergency"),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
89#[repr(u8)]
90pub enum DetailLevel {
91 DMinimal = 0,
93 DNormal = 1,
95 DDetailed = 2,
97 DMaximum = 3,
99}
100
101impl Encode<()> for DetailLevel {
102 fn encode<W: pallas_codec::minicbor::encode::Write>(
103 &self,
104 e: &mut pallas_codec::minicbor::Encoder<W>,
105 _ctx: &mut (),
106 ) -> Result<(), pallas_codec::minicbor::encode::Error<W::Error>> {
107 e.array(1)?.u8(*self as u8)?;
109 Ok(())
110 }
111}
112
113impl<'b> Decode<'b, ()> for DetailLevel {
114 fn decode(
115 d: &mut pallas_codec::minicbor::Decoder<'b>,
116 _ctx: &mut (),
117 ) -> Result<Self, pallas_codec::minicbor::decode::Error> {
118 d.array()?;
119 let val = d.u8()?;
120 match val {
121 0 => Ok(DetailLevel::DMinimal),
122 1 => Ok(DetailLevel::DNormal),
123 2 => Ok(DetailLevel::DDetailed),
124 3 => Ok(DetailLevel::DMaximum),
125 _ => Err(pallas_codec::minicbor::decode::Error::message(
126 "invalid detail level",
127 )),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct TraceObject {
149 pub to_human: Option<String>,
151 pub to_machine: String,
153 pub to_namespace: Vec<String>,
155 pub to_severity: Severity,
157 pub to_details: DetailLevel,
159 pub to_timestamp: DateTime<Utc>,
161 pub to_hostname: String,
163 pub to_thread_id: String,
165}
166
167impl Encode<()> for TraceObject {
168 fn encode<W: pallas_codec::minicbor::encode::Write>(
169 &self,
170 e: &mut pallas_codec::minicbor::Encoder<W>,
171 ctx: &mut (),
172 ) -> Result<(), pallas_codec::minicbor::encode::Error<W::Error>> {
173 e.array(9)?;
177 e.u8(0)?;
178
179 match &self.to_human {
181 Some(h) => {
182 e.array(1)?;
183 e.str(h)?;
184 }
185 None => {
186 e.array(0)?;
187 }
188 }
189
190 e.str(&self.to_machine)?;
192
193 e.array(self.to_namespace.len() as u64)?;
195 for ns in &self.to_namespace {
196 e.str(ns)?;
197 }
198
199 self.to_severity.encode(e, ctx)?;
201
202 self.to_details.encode(e, ctx)?;
204
205 let secs = self.to_timestamp.timestamp();
210 let psecs = self.to_timestamp.timestamp_subsec_nanos() as u64 * 1_000;
211 e.tag(pallas_codec::minicbor::data::Tag::new(1000))?;
212 e.map(2)?;
213 e.u8(1)?;
214 e.i64(secs)?;
215 e.i64(-12)?;
216 e.u64(psecs)?;
217
218 e.str(&self.to_hostname)?;
220
221 e.str(&self.to_thread_id)?;
223
224 Ok(())
225 }
226}
227
228impl<'b> Decode<'b, ()> for TraceObject {
229 fn decode(
230 d: &mut pallas_codec::minicbor::Decoder<'b>,
231 ctx: &mut (),
232 ) -> Result<Self, pallas_codec::minicbor::decode::Error> {
233 let len = d.array()?;
234 if len != Some(9) {
235 return Err(pallas_codec::minicbor::decode::Error::message(
236 "TraceObject must have 9 elements (constructor index + 8 fields)",
237 ));
238 }
239 let _constructor_idx = d.u8()?;
241
242 let to_human = {
244 let opt_len = d.array()?;
245 match opt_len {
246 Some(0) => None,
247 Some(1) => Some(d.str()?.to_string()),
248 _ => {
249 return Err(pallas_codec::minicbor::decode::Error::message(
250 "invalid Maybe encoding",
251 ));
252 }
253 }
254 };
255
256 let to_machine = d.str()?.to_string();
258
259 let mut to_namespace = Vec::new();
262 for s in d.array_iter::<String>()? {
263 to_namespace.push(s?);
264 }
265
266 let to_severity = Severity::decode(d, ctx)?;
268
269 let to_details = DetailLevel::decode(d, ctx)?;
271
272 let tag = d.tag()?;
276 let to_timestamp = if tag == pallas_codec::minicbor::data::Tag::new(1000) {
277 let map_len = d.map()?;
278 if map_len != Some(2) {
279 return Err(pallas_codec::minicbor::decode::Error::message(
280 "expected map of length 2 for UTCTime (tag 1000)",
281 ));
282 }
283 let k0 = d.i64()?;
284 if k0 != 1 {
285 return Err(pallas_codec::minicbor::decode::Error::message(
286 "expected key 1 (secs) in tag-1000 UTCTime",
287 ));
288 }
289 let secs = d.i64()?;
290 let k1 = d.i64()?;
291 if k1 != -12 {
292 return Err(pallas_codec::minicbor::decode::Error::message(
293 "expected key -12 (psecs) in tag-1000 UTCTime",
294 ));
295 }
296 let psecs = d.u64()?;
297 let nanos = (psecs / 1_000) as u32;
298 DateTime::from_timestamp(secs, nanos).ok_or_else(|| {
299 pallas_codec::minicbor::decode::Error::message("invalid timestamp")
300 })?
301 } else if tag == pallas_codec::minicbor::data::Tag::new(1) {
302 let timestamp_f64 = d.f64()?;
304 let secs = timestamp_f64.floor() as i64;
305 let nanos = ((timestamp_f64 - secs as f64) * 1_000_000_000.0) as u32;
306 DateTime::from_timestamp(secs, nanos).ok_or_else(|| {
307 pallas_codec::minicbor::decode::Error::message("invalid timestamp")
308 })?
309 } else {
310 return Err(pallas_codec::minicbor::decode::Error::message(
311 "expected UTCTime tag (1000 or 1)",
312 ));
313 };
314
315 let to_hostname = d.str()?.to_string();
317
318 let to_thread_id = d.str()?.to_string();
320
321 Ok(TraceObject {
322 to_human,
323 to_machine,
324 to_namespace,
325 to_severity,
326 to_details,
327 to_timestamp,
328 to_hostname,
329 to_thread_id,
330 })
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use pallas_codec::minicbor;
338
339 fn encode<T: minicbor::Encode<()>>(value: &T) -> Vec<u8> {
340 let mut buf = Vec::new();
341 minicbor::encode_with(value, &mut buf, &mut ()).unwrap();
342 buf
343 }
344
345 fn decode<T: for<'b> minicbor::Decode<'b, ()>>(buf: &[u8]) -> T {
346 minicbor::decode_with(buf, &mut ()).unwrap()
347 }
348
349 #[test]
352 fn test_severity_encoding() {
353 assert_eq!(decode::<Severity>(&encode(&Severity::Info)), Severity::Info);
355 }
356
357 #[test]
358 fn all_severity_variants_round_trip() {
359 for sev in [
360 Severity::Debug,
361 Severity::Info,
362 Severity::Notice,
363 Severity::Warning,
364 Severity::Error,
365 Severity::Critical,
366 Severity::Alert,
367 Severity::Emergency,
368 ] {
369 let decoded = decode::<Severity>(&encode(&sev));
370 assert_eq!(decoded, sev, "Severity::{:?} round-trip failed", sev);
371 }
372 }
373
374 #[test]
375 fn severity_encoded_as_array1_constructor_index() {
376 assert_eq!(encode(&Severity::Debug), &[0x81, 0x00]);
379 assert_eq!(encode(&Severity::Emergency), &[0x81, 0x07]);
381 }
382
383 #[test]
386 fn test_detail_level_encoding() {
387 assert_eq!(
388 decode::<DetailLevel>(&encode(&DetailLevel::DNormal)),
389 DetailLevel::DNormal
390 );
391 }
392
393 #[test]
394 fn all_detail_level_variants_round_trip() {
395 for dl in [
396 DetailLevel::DMinimal,
397 DetailLevel::DNormal,
398 DetailLevel::DDetailed,
399 DetailLevel::DMaximum,
400 ] {
401 let decoded = decode::<DetailLevel>(&encode(&dl));
402 assert_eq!(decoded, dl, "DetailLevel::{:?} round-trip failed", dl);
403 }
404 }
405
406 fn make_trace_object(to_human: Option<&str>) -> TraceObject {
409 TraceObject {
410 to_human: to_human.map(str::to_string),
411 to_machine: r#"{"k":1}"#.to_string(),
412 to_namespace: vec!["Cardano".to_string(), "Node".to_string()],
413 to_severity: Severity::Warning,
414 to_details: DetailLevel::DDetailed,
415 to_timestamp: chrono::DateTime::from_timestamp(1_700_000_000, 500_000_000).unwrap(),
416 to_hostname: "node-1".to_string(),
417 to_thread_id: "99".to_string(),
418 }
419 }
420
421 #[test]
422 fn trace_object_with_human_round_trip() {
423 let original = make_trace_object(Some("human readable"));
424 let decoded = decode::<TraceObject>(&encode(&original));
425 assert_eq!(decoded.to_human, Some("human readable".to_string()));
426 assert_eq!(decoded.to_machine, original.to_machine);
427 assert_eq!(decoded.to_namespace, original.to_namespace);
428 assert_eq!(decoded.to_severity, original.to_severity);
429 assert_eq!(decoded.to_details, original.to_details);
430 assert_eq!(decoded.to_hostname, original.to_hostname);
431 assert_eq!(decoded.to_thread_id, original.to_thread_id);
432 assert_eq!(
434 decoded.to_timestamp.timestamp(),
435 original.to_timestamp.timestamp()
436 );
437 assert_eq!(
438 decoded.to_timestamp.timestamp_subsec_nanos(),
439 original.to_timestamp.timestamp_subsec_nanos()
440 );
441 }
442
443 #[test]
444 fn trace_object_without_human_round_trip() {
445 let original = make_trace_object(None);
446 let decoded = decode::<TraceObject>(&encode(&original));
447 assert_eq!(decoded.to_human, None);
448 }
449
450 #[test]
451 fn trace_object_empty_namespace_round_trip() {
452 let mut original = make_trace_object(None);
453 original.to_namespace = vec![];
454 let decoded = decode::<TraceObject>(&encode(&original));
455 assert!(decoded.to_namespace.is_empty());
456 }
457
458 #[test]
459 fn timestamp_tag1_compat_decode() {
460 let original = make_trace_object(None);
464 let normal_buf = encode(&original);
465
466 let secs = original.to_timestamp.timestamp() as f64;
470 let mut buf: Vec<u8> = Vec::new();
471 let mut enc = minicbor::Encoder::new(&mut buf);
472
473 enc.array(9).unwrap().u8(0).unwrap();
475 enc.array(0).unwrap();
477 enc.str(r#"{"k":1}"#).unwrap();
479 enc.array(0).unwrap();
481 enc.array(1).unwrap().u8(3).unwrap();
483 enc.array(1).unwrap().u8(2).unwrap();
485 enc.tag(minicbor::data::Tag::new(1))
487 .unwrap()
488 .f64(secs)
489 .unwrap();
490 enc.str("node-1").unwrap();
492 enc.str("99").unwrap();
494
495 let decoded = decode::<TraceObject>(&buf);
496 assert_eq!(
497 decoded.to_timestamp.timestamp(),
498 original.to_timestamp.timestamp()
499 );
500 let _ = normal_buf;
502 }
503}