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