1use smallvec::SmallVec;
2
3use crate::error::Error;
4use crate::key::Key;
5use crate::log::LogEntry;
6use crate::value::Value;
7
8pub(crate) type ConflictFn<K> = fn(&mut WideEvent<K>, K);
9
10const INLINE_CAP: usize = 32;
11const LOG_INLINE_CAP: usize = 16;
12
13#[derive(Clone)]
29pub struct WideEvent<K: Key> {
30 pub(crate) values: SmallVec<[Option<Value<K>>; INLINE_CAP]>,
33 pub(crate) log_entries: SmallVec<[LogEntry<K>; LOG_INLINE_CAP]>,
35 pub(crate) on_type_conflict: Option<ConflictFn<K>>,
37}
38
39impl<K: Key> WideEvent<K> {
40 #[inline]
42 pub(crate) fn new() -> Self {
43 Self {
44 values: SmallVec::new(),
45 log_entries: SmallVec::new(),
46 on_type_conflict: None,
47 }
48 }
49
50 #[inline]
52 pub(crate) fn ensure_capacity(&mut self, idx: usize) {
53 if idx >= self.values.len() {
54 self.values.resize(idx + 1, None);
55 }
56 }
57
58 #[inline]
60 pub(crate) fn add<V: Into<Value<K>>>(&mut self, key: K, value: V) {
61 let idx = key.as_index();
62 self.ensure_capacity(idx);
63 self.values[idx] = Some(value.into());
64 }
65
66 #[inline]
68 pub(crate) fn object(&mut self, key: K) -> &mut WideEvent<K> {
69 let idx = key.as_index();
70 self.ensure_capacity(idx);
71 let needs_replace = match &self.values[idx] {
72 None => true,
73 Some(v) => !v.is_object(),
74 };
75 if needs_replace {
76 if let Some(cb) = self.on_type_conflict
77 && self.values[idx].is_some()
78 {
79 cb(self, key);
80 }
81 self.values[idx] = Some(Value::from_object(self.new_child()));
82 }
83 match &mut self.values[idx] {
84 Some(Value::Object(boxed)) => boxed,
85 _ => unreachable!(),
86 }
87 }
88
89 #[inline]
90 pub(crate) fn new_child(&self) -> WideEvent<K> {
91 WideEvent {
92 values: SmallVec::new(),
93 log_entries: SmallVec::new(),
94 on_type_conflict: self.on_type_conflict,
95 }
96 }
97
98 #[inline]
100 pub fn add_path<V: Into<Value<K>>>(&mut self, path: &[K], value: V) {
101 debug_assert!(!path.is_empty(), "path must have at least one segment");
102 let value = value.into();
103 if path.len() == 1 {
104 self.add(path[0], value);
105 return;
106 }
107 let target = self.descend_mut(path);
108 target.add(path[path.len() - 1], value);
109 }
110
111 #[inline]
112 fn descend_mut(&mut self, path: &[K]) -> &mut WideEvent<K> {
113 let mut current: &mut WideEvent<K> = self;
114 for &seg in &path[..path.len() - 1] {
115 current = current.object(seg);
116 }
117 current
118 }
119
120 #[inline]
122 pub(crate) fn inc(&mut self, key: K) {
123 let idx = key.as_index();
124 self.ensure_capacity(idx);
125 let new_val = match &self.values[idx] {
126 Some(Value::U64(n)) => Value::from(*n + 1),
127 Some(Value::I64(n)) => Value::from(*n + 1),
128 Some(_) => Value::from(1u64),
129 None => Value::from(1u64),
130 };
131 self.values[idx] = Some(new_val);
132 }
133
134 #[inline]
136 pub(crate) fn dec(&mut self, key: K) {
137 let idx = key.as_index();
138 self.ensure_capacity(idx);
139 let new_val = match &self.values[idx] {
140 Some(Value::U64(n)) => {
141 if *n > 0 {
142 Value::from(*n - 1)
143 } else {
144 Value::from(0u64)
145 }
146 }
147 Some(Value::I64(n)) => Value::from(*n - 1),
148 Some(_) => Value::from(-1i64),
149 None => Value::from(-1i64),
150 };
151 self.values[idx] = Some(new_val);
152 }
153
154 #[inline]
156 pub(crate) fn add_n(&mut self, key: K, n: i64) {
157 let idx = key.as_index();
158 self.ensure_capacity(idx);
159 let new_val = match &self.values[idx] {
160 Some(Value::U64(u)) => Value::from(u.saturating_add_signed(n)),
161 Some(Value::I64(i)) => Value::from(*i + n),
162 Some(_) => {
163 if n >= 0 {
164 Value::from(n as u64)
165 } else {
166 Value::from(n)
167 }
168 }
169 None => {
170 if n >= 0 {
171 Value::from(n as u64)
172 } else {
173 Value::from(n)
174 }
175 }
176 };
177 self.values[idx] = Some(new_val);
178 }
179
180 #[inline]
181 pub fn inc_path(&mut self, path: &[K]) {
182 debug_assert!(!path.is_empty(), "path must have at least one segment");
183 if path.len() == 1 {
184 self.inc(path[0]);
185 return;
186 }
187 let target = self.descend_mut(path);
188 target.inc(path[path.len() - 1]);
189 }
190
191 #[inline]
192 pub fn dec_path(&mut self, path: &[K]) {
193 debug_assert!(!path.is_empty(), "path must have at least one segment");
194 if path.len() == 1 {
195 self.dec(path[0]);
196 return;
197 }
198 let target = self.descend_mut(path);
199 target.dec(path[path.len() - 1]);
200 }
201
202 #[inline]
203 pub fn add_n_path(&mut self, path: &[K], n: i64) {
204 debug_assert!(!path.is_empty(), "path must have at least one segment");
205 if path.len() == 1 {
206 self.add_n(path[0], n);
207 return;
208 }
209 let target = self.descend_mut(path);
210 target.add_n(path[path.len() - 1], n);
211 }
212
213 #[inline]
214 pub fn append_log_entry(&mut self, level: &'static str, message: &str) {
215 self.log_entries.push(LogEntry::new(
216 level,
217 crate::log::LogMsg::Owned(faststr::FastStr::new(message)),
218 ));
219 }
220
221 #[inline]
223 pub fn append_log_entry_static(&mut self, level: &'static str, message: &'static str) {
224 self.log_entries
225 .push(LogEntry::new(level, crate::log::LogMsg::Static(message)));
226 }
227
228 #[inline]
229 pub(crate) fn len(&self) -> usize {
230 self.values.iter().filter(|v| v.is_some()).count()
231 }
232
233 pub fn to_json(&self) -> Result<String, Error> {
234 sonic_rs::to_string(self).map_err(|e| Error::Serialize(e.to_string()))
235 }
236
237 pub fn serialize_to<W: std::io::Write>(&self, w: &mut W) -> Result<(), Error> {
240 write_event(self, w).map_err(|e| Error::Serialize(e.to_string()))
241 }
242
243 #[inline]
245 pub(crate) fn count_present(&self) -> usize {
246 self.values.iter().filter(|v| v.is_some()).count()
247 }
248}
249
250#[cfg(test)]
251impl<K: Key> WideEvent<K> {
252 #[inline]
254 pub(crate) fn new_with_warnings(f: ConflictFn<K>) -> Self {
255 Self {
256 values: SmallVec::new(),
257 log_entries: SmallVec::new(),
258 on_type_conflict: Some(f),
259 }
260 }
261
262 #[inline]
263 pub(crate) fn is_empty(&self) -> bool {
264 self.values.iter().all(|v| v.is_none())
265 }
266
267 #[inline]
268 pub(crate) fn log_len(&self) -> usize {
269 self.log_entries.len()
270 }
271
272 #[inline]
273 pub(crate) fn has_logs(&self) -> bool {
274 !self.log_entries.is_empty()
275 }
276}
277
278impl<K: Key> Default for WideEvent<K> {
279 fn default() -> Self {
280 Self::new()
281 }
282}
283
284impl<K: Key> serde::Serialize for WideEvent<K> {
285 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
286 use serde::ser::SerializeMap;
287 let present = self.count_present();
288 let total = present + if self.log_entries.is_empty() { 0 } else { 1 };
289 let mut map = serializer.serialize_map(Some(total))?;
290 for (i, key) in K::KEYS.iter().enumerate() {
291 if i < self.values.len()
292 && let Some(value) = &self.values[i]
293 {
294 map.serialize_entry(key.as_str(), value)?;
295 }
296 }
297 if !self.log_entries.is_empty() {
298 map.serialize_entry(K::LOG_KEY, &self.log_entries)?;
299 }
300 map.end()
301 }
302}
303
304impl<K: Key> std::fmt::Debug for WideEvent<K> {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 f.debug_struct("WideEvent")
307 .field("present", &self.len())
308 .field("log_entries_len", &self.log_entries.len())
309 .field("has_conflict_callback", &self.on_type_conflict.is_some())
310 .finish()
311 }
312}
313
314#[inline]
317fn write_event<K: Key, W: std::io::Write>(ev: &WideEvent<K>, w: &mut W) -> std::io::Result<()> {
318 w.write_all(b"{")?;
319 let mut first = true;
320 for (i, key) in K::KEYS.iter().enumerate() {
321 if i < ev.values.len()
322 && let Some(val) = &ev.values[i]
323 {
324 if !first {
325 w.write_all(b",")?;
326 }
327 first = false;
328 write_json_str(w, key.as_str())?;
329 w.write_all(b":")?;
330 write_value(val, w)?;
331 }
332 }
333 if !ev.log_entries.is_empty() {
334 if !first {
335 w.write_all(b",")?;
336 }
337 write_json_str(w, K::LOG_KEY)?;
338 w.write_all(b":")?;
339 w.write_all(b"[")?;
340 for (j, entry) in ev.log_entries.iter().enumerate() {
341 if j > 0 {
342 w.write_all(b",")?;
343 }
344 w.write_all(b"{")?;
345 write_json_str(w, K::LEVEL_KEY)?;
346 w.write_all(b":")?;
347 write_json_str(w, entry.level)?;
348 w.write_all(b",")?;
349 write_json_str(w, K::MESSAGE_KEY)?;
350 w.write_all(b":")?;
351 write_json_str(w, entry.message.as_str())?;
352 w.write_all(b"}")?;
353 }
354 w.write_all(b"]")?;
355 }
356 w.write_all(b"}")?;
357 Ok(())
358}
359
360#[inline]
361fn write_value<K: Key, W: std::io::Write>(
362 val: &crate::value::Value<K>,
363 w: &mut W,
364) -> std::io::Result<()> {
365 match val {
366 Value::Null => w.write_all(b"null"),
367 Value::Bool(b) => {
368 if *b {
369 w.write_all(b"true")
370 } else {
371 w.write_all(b"false")
372 }
373 }
374 Value::I64(i) => {
375 let mut buf = itoa::Buffer::new();
376 w.write_all(buf.format(*i).as_bytes())
377 }
378 Value::U64(u) => {
379 let mut buf = itoa::Buffer::new();
380 w.write_all(buf.format(*u).as_bytes())
381 }
382 Value::F64(f) => {
383 let mut buf = ryu::Buffer::new();
384 w.write_all(buf.format(*f).as_bytes())
385 }
386 Value::Str(s) => write_json_str(w, s.as_str()),
387 Value::StaticStr(s) => write_json_str(w, s),
388 Value::Array(arr) => {
389 w.write_all(b"[")?;
390 for (i, v) in arr.iter().enumerate() {
391 if i > 0 {
392 w.write_all(b",")?;
393 }
394 write_value(v, w)?;
395 }
396 w.write_all(b"]")?;
397 Ok(())
398 }
399 Value::Object(obj) => write_event(obj, w),
400 }
401}
402
403#[inline]
404fn write_json_str<W: std::io::Write>(w: &mut W, s: &str) -> std::io::Result<()> {
405 w.write_all(b"\"")?;
406 for c in s.bytes() {
407 match c {
408 b'"' => w.write_all(b"\\\"")?,
409 b'\\' => w.write_all(b"\\\\")?,
410 b'\n' => w.write_all(b"\\n")?,
411 b'\t' => w.write_all(b"\\t")?,
412 b'\r' => w.write_all(b"\\r")?,
413 0x08 => w.write_all(b"\\b")?,
414 0x0c => w.write_all(b"\\f")?,
415 c if c < 0x20 => {
416 w.write_all(format!("\\u{:04x}", c).as_bytes())?;
417 }
418 c => w.write_all(&[c])?,
419 }
420 }
421 w.write_all(b"\"")?;
422 Ok(())
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use crate::key::test_support::TestKey;
429
430 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
432 #[repr(u8)]
433 #[allow(dead_code)]
434 enum BigKey {
435 Duration,
436 TotalMs,
437 Event,
438 Timestamp,
439 Id,
440 K2,
441 K3,
442 K4,
443 K5,
444 K6,
445 K7,
446 K8,
447 K9,
448 K10,
449 K11,
450 K12,
451 K13,
452 K14,
453 K15,
454 K16,
455 K17,
456 K18,
457 K19,
458 K20,
459 K21,
460 K22,
461 }
462
463 impl crate::key::Key for BigKey {
464 fn as_str(self) -> &'static str {
465 match self {
466 BigKey::Duration => "duration",
467 BigKey::TotalMs => "total_ms",
468 BigKey::Event => "event",
469 BigKey::Timestamp => "timestamp",
470 BigKey::Id => "id",
471 BigKey::K2 => "k2",
472 BigKey::K3 => "k3",
473 BigKey::K4 => "k4",
474 BigKey::K5 => "k5",
475 BigKey::K6 => "k6",
476 BigKey::K7 => "k7",
477 BigKey::K8 => "k8",
478 BigKey::K9 => "k9",
479 BigKey::K10 => "k10",
480 BigKey::K11 => "k11",
481 BigKey::K12 => "k12",
482 BigKey::K13 => "k13",
483 BigKey::K14 => "k14",
484 BigKey::K15 => "k15",
485 BigKey::K16 => "k16",
486 BigKey::K17 => "k17",
487 BigKey::K18 => "k18",
488 BigKey::K19 => "k19",
489 BigKey::K20 => "k20",
490 BigKey::K21 => "k21",
491 BigKey::K22 => "k22",
492 }
493 }
494 const MAX_KEYS: usize = 25;
495 const KEYS: &'static [Self] = &[
496 BigKey::Duration,
497 BigKey::TotalMs,
498 BigKey::Event,
499 BigKey::Timestamp,
500 BigKey::Id,
501 BigKey::K2,
502 BigKey::K3,
503 BigKey::K4,
504 BigKey::K5,
505 BigKey::K6,
506 BigKey::K7,
507 BigKey::K8,
508 BigKey::K9,
509 BigKey::K10,
510 BigKey::K11,
511 BigKey::K12,
512 BigKey::K13,
513 BigKey::K14,
514 BigKey::K15,
515 BigKey::K16,
516 BigKey::K17,
517 BigKey::K18,
518 BigKey::K19,
519 BigKey::K20,
520 BigKey::K21,
521 BigKey::K22,
522 ];
523 const KEY_STRS: &'static [&'static str] = &[
524 "duration",
525 "total_ms",
526 "event",
527 "timestamp",
528 "id",
529 "k2",
530 "k3",
531 "k4",
532 "k5",
533 "k6",
534 "k7",
535 "k8",
536 "k9",
537 "k10",
538 "k11",
539 "k12",
540 "k13",
541 "k14",
542 "k15",
543 "k16",
544 "k17",
545 "k18",
546 "k19",
547 "k20",
548 "k21",
549 "k22",
550 ];
551 fn as_index(self) -> usize {
552 self as usize
553 }
554 const DURATION_PATH: &'static [Self] = &[BigKey::Duration, BigKey::TotalMs];
555 const TIMESTAMP_PATH: &'static [Self] = &[BigKey::Event, BigKey::Timestamp];
556 const ID_PATH: &'static [Self] = &[BigKey::Event, BigKey::Id];
557 const LOG_KEY: &'static str = "log";
558 const LEVEL_KEY: &'static str = "level";
559 const MESSAGE_KEY: &'static str = "message";
560 }
561
562 #[test]
565 fn new_is_empty() {
566 let e = WideEvent::<TestKey>::new();
567 assert!(e.is_empty());
568 assert_eq!(e.len(), 0);
569 assert_eq!(e.log_len(), 0);
570 assert!(!e.has_logs());
571 }
572
573 #[test]
574 fn default_is_empty() {
575 let e = WideEvent::<TestKey>::default();
576 assert!(e.is_empty());
577 }
578
579 #[test]
580 fn add_single() {
581 let mut e = WideEvent::<TestKey>::new();
582 e.add(TestKey::Status, "ok");
583 assert_eq!(e.len(), 1);
584 }
585
586 #[test]
587 fn add_updates_existing() {
588 let mut e = WideEvent::<TestKey>::new();
589 e.add(TestKey::Status, "ok");
590 e.add(TestKey::Status, "err");
591 assert_eq!(e.len(), 1);
592 assert_eq!(e.to_json().unwrap(), r#"{"status":"err"}"#);
593 }
594
595 #[test]
596 fn add_multiple_keys() {
597 let mut e = WideEvent::<TestKey>::new();
598 e.add(TestKey::Requests, 42u64);
599 e.add(TestKey::Status, "ok");
600 e.add(TestKey::Tag, "web");
601 assert_eq!(e.len(), 3);
602 }
603
604 #[test]
607 fn object_creates_nested() {
608 let mut e = WideEvent::<TestKey>::new();
609 e.object(TestKey::Details).add(TestKey::Status, "ok");
610 let json = e.to_json().unwrap();
611 assert!(json.contains("\"details\""));
612 assert!(json.contains("\"status\""));
613 }
614
615 #[test]
616 fn object_returns_existing() {
617 let mut e = WideEvent::<TestKey>::new();
618 e.object(TestKey::Details).add(TestKey::Requests, 1u64);
619 e.object(TestKey::Details).add(TestKey::Status, "ok");
620 let json = e.to_json().unwrap();
621 assert!(json.contains("\"requests\""));
622 assert!(json.contains("\"status\""));
623 assert_eq!(e.len(), 1, "only one top-level entry");
624 }
625
626 #[test]
629 fn object_type_conflict_replaces_with_object() {
630 let mut e = WideEvent::<TestKey>::new();
631 e.add(TestKey::Details, true);
632 e.object(TestKey::Details).add(TestKey::Status, "ok");
633 assert!(matches!(&e.values[TestKey::Details.as_index()], Some(v) if v.is_object()));
634 }
635
636 #[test]
637 fn object_type_conflict_fires_callback() {
638 use std::sync::atomic::{AtomicU32, Ordering};
639
640 static COUNTER: AtomicU32 = AtomicU32::new(0);
641 COUNTER.store(0, Ordering::SeqCst);
642
643 fn cb(_event: &mut WideEvent<TestKey>, _key: TestKey) {
644 COUNTER.fetch_add(1, Ordering::SeqCst);
645 }
646
647 let mut e = WideEvent::new_with_warnings(cb);
648 e.add(TestKey::Details, true);
649 e.object(TestKey::Details);
650 assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
651 }
652
653 #[test]
656 fn to_json_valid() {
657 let mut e = WideEvent::<TestKey>::new();
658 e.add(TestKey::Requests, 7u64);
659 e.add(TestKey::Status, "ok");
660 e.add(TestKey::Flag, false);
661 let json = e.to_json().unwrap();
662 assert!(json.starts_with('{'));
663 assert!(json.ends_with('}'));
664 }
665
666 #[test]
667 fn to_json_roundtrip() {
668 let mut e = WideEvent::<TestKey>::new();
669 e.add(TestKey::Requests, 42u64);
670 e.add(TestKey::Status, "active");
671 let json = e.to_json().unwrap();
672 let parsed: sonic_rs::Value = sonic_rs::from_str(&json).unwrap();
673 assert_eq!(parsed["requests"], 42u64);
674 assert_eq!(parsed["status"], "active");
675 }
676
677 #[test]
678 fn serialize_nested_object() {
679 let mut e = WideEvent::<TestKey>::new();
680 e.object(TestKey::Service).add(TestKey::Name, "svc");
681 assert_eq!(e.to_json().unwrap(), r#"{"service":{"name":"svc"}}"#);
682 }
683
684 #[test]
685 fn inline_capacity_not_spilled() {
686 let mut e = WideEvent::<BigKey>::new();
687 for i in 0..BigKey::MAX_KEYS {
688 e.add(BigKey::KEYS[i], i as u64);
689 }
690 assert_eq!(e.len(), BigKey::MAX_KEYS);
691 assert!(
692 !e.values.spilled(),
693 "{} entries must fit in inline storage",
694 BigKey::MAX_KEYS
695 );
696 }
697
698 #[test]
701 fn add_path_single_segment() {
702 let mut e = WideEvent::<TestKey>::new();
703 e.add_path(&[TestKey::Status], "ok");
704 assert_eq!(e.len(), 1);
705 assert_eq!(e.to_json().unwrap(), r#"{"status":"ok"}"#);
706 }
707
708 #[test]
709 fn add_path_two_segments() {
710 let mut e = WideEvent::<TestKey>::new();
711 e.add_path(&[TestKey::Service, TestKey::Name], "my-service");
712 assert_eq!(e.to_json().unwrap(), r#"{"service":{"name":"my-service"}}"#);
713 }
714
715 #[test]
716 fn add_path_updates_existing_nested() {
717 let mut e = WideEvent::<TestKey>::new();
718 e.add_path(&[TestKey::Service, TestKey::Name], "a");
719 e.add_path(&[TestKey::Service, TestKey::Version], "1.0.0");
720 e.add_path(&[TestKey::Service, TestKey::Name], "b");
721 let json = e.to_json().unwrap();
722 assert!(json.contains(r#""name":"b""#));
724 assert!(json.contains(r#""version":"1.0.0""#));
725 }
726
727 #[test]
728 fn add_path_duration_path() {
729 use crate::key::Key;
730 let mut e = WideEvent::<TestKey>::new();
731 e.add_path(<TestKey as Key>::DURATION_PATH, 42u64);
732 let json = e.to_json().unwrap();
733 assert_eq!(json, r#"{"duration":{"total_ms":42}}"#);
734 }
735
736 #[test]
739 fn inc_initializes_to_one() {
740 let mut e = WideEvent::<TestKey>::new();
741 e.inc(TestKey::Requests);
742 assert_eq!(e.to_json().unwrap(), r#"{"requests":1}"#);
743 }
744
745 #[test]
746 fn inc_increments_existing() {
747 let mut e = WideEvent::<TestKey>::new();
748 e.add(TestKey::Requests, 41u64);
749 e.inc(TestKey::Requests);
750 assert_eq!(e.to_json().unwrap(), r#"{"requests":42}"#);
751 }
752
753 #[test]
754 fn dec_initializes_to_minus_one() {
755 let mut e = WideEvent::<TestKey>::new();
756 e.dec(TestKey::Requests);
757 assert_eq!(e.to_json().unwrap(), r#"{"requests":-1}"#);
758 }
759
760 #[test]
761 fn dec_decrements_existing() {
762 let mut e = WideEvent::<TestKey>::new();
763 e.add(TestKey::Requests, 5u64);
764 e.dec(TestKey::Requests);
765 assert_eq!(e.to_json().unwrap(), r#"{"requests":4}"#);
766 }
767
768 #[test]
769 fn add_n_initializes() {
770 let mut e = WideEvent::<TestKey>::new();
771 e.add_n(TestKey::Requests, 10);
772 assert_eq!(e.to_json().unwrap(), r#"{"requests":10}"#);
773 }
774
775 #[test]
776 fn add_n_adds_to_existing() {
777 let mut e = WideEvent::<TestKey>::new();
778 e.add(TestKey::Requests, 40u64);
779 e.add_n(TestKey::Requests, 2);
780 assert_eq!(e.to_json().unwrap(), r#"{"requests":42}"#);
781 }
782
783 #[test]
784 fn add_n_negative_from_u64() {
785 let mut e = WideEvent::<TestKey>::new();
786 e.add(TestKey::Requests, 10u64);
787 e.add_n(TestKey::Requests, -3);
788 assert_eq!(e.to_json().unwrap(), r#"{"requests":7}"#);
789 }
790
791 #[test]
794 fn inc_path_single_segment() {
795 let mut e = WideEvent::<TestKey>::new();
796 e.inc_path(&[TestKey::Requests]);
797 e.inc_path(&[TestKey::Requests]);
798 e.inc_path(&[TestKey::Requests]);
799 assert_eq!(e.to_json().unwrap(), r#"{"requests":3}"#);
800 }
801
802 #[test]
803 fn dec_path_single_segment() {
804 let mut e = WideEvent::<TestKey>::new();
805 e.dec_path(&[TestKey::Requests]);
806 assert_eq!(e.to_json().unwrap(), r#"{"requests":-1}"#);
807 }
808
809 #[test]
810 fn add_n_path_single_segment() {
811 let mut e = WideEvent::<TestKey>::new();
812 e.add_n_path(&[TestKey::Requests], 5);
813 e.add_n_path(&[TestKey::Requests], -2);
814 assert_eq!(e.to_json().unwrap(), r#"{"requests":3}"#);
815 }
816
817 #[test]
818 fn inc_path_two_segments() {
819 let mut e = WideEvent::<TestKey>::new();
820 e.add_path(&[TestKey::Service, TestKey::Requests], 0u64);
821 e.inc_path(&[TestKey::Service, TestKey::Requests]);
822 e.inc_path(&[TestKey::Service, TestKey::Requests]);
823 let json = e.to_json().unwrap();
824 assert!(json.contains(r#""requests":2"#));
825 }
826
827 #[test]
828 fn add_n_path_two_segments() {
829 let mut e = WideEvent::<TestKey>::new();
830 e.add_n_path(&[TestKey::Service, TestKey::Requests], 10);
831 e.add_n_path(&[TestKey::Service, TestKey::Requests], -3);
832 let json = e.to_json().unwrap();
833 assert!(json.contains(r#""requests":7"#));
834 }
835
836 #[test]
839 fn append_log_entry_single() {
840 let mut e = WideEvent::<TestKey>::new();
841 e.append_log_entry("info", "request received");
842 assert!(e.has_logs());
843 assert_eq!(e.log_len(), 1);
844 let json = e.to_json().unwrap();
845 assert_eq!(
846 json,
847 r#"{"log":[{"level":"info","message":"request received"}]}"#
848 );
849 }
850
851 #[test]
852 fn append_log_entry_multiple_preserves_order() {
853 let mut e = WideEvent::<TestKey>::new();
854 e.append_log_entry("info", "first");
855 e.append_log_entry("warn", "second");
856 e.append_log_entry("error", "third");
857 assert_eq!(e.log_len(), 3);
858 let json = e.to_json().unwrap();
859 assert_eq!(
860 json,
861 concat!(
862 r#"{"log":["#,
863 r#"{"level":"info","message":"first"},"#,
864 r#"{"level":"warn","message":"second"},"#,
865 r#"{"level":"error","message":"third"}"#,
866 r#"]}"#,
867 )
868 );
869 }
870
871 #[test]
872 fn log_entries_after_user_entries() {
873 let mut e = WideEvent::<TestKey>::new();
874 e.add(TestKey::Status, "ok");
875 e.append_log_entry("info", "done");
876 let json = e.to_json().unwrap();
877 assert_eq!(
878 json,
879 r#"{"status":"ok","log":[{"level":"info","message":"done"}]}"#
880 );
881 }
882
883 #[test]
884 fn no_log_key_when_empty() {
885 let mut e = WideEvent::<TestKey>::new();
886 e.add(TestKey::Status, "ok");
887 let json = e.to_json().unwrap();
888 assert_eq!(json, r#"{"status":"ok"}"#);
889 assert!(!json.contains("log"));
890 }
891}