harness/contexts/
event.rs1use rand::{Rng, RngExt};
9
10use super::{fresh_timestamp, get_bytes, get_tags, put_bytes, put_tags};
11use super::{LEN_DIGITS, TS_DIGITS};
12use crate::payload::dogstatsd::common;
13
14const OPT_PREFIXES: &[&[u8]] = &[b"k:", b"h:", b"s:", b"t:", b"p:"];
17
18const OPT_COUNTS: &[usize] = &[0, 0, 1, 1, 2, 3];
20
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
23pub struct EventContext {
24 pub title: Vec<u8>,
26 pub tags: Vec<Vec<u8>>,
28 pub options: Vec<Vec<u8>>,
30}
31
32impl EventContext {
33 pub(crate) fn mint_within(rng: &mut (impl Rng + ?Sized), budget: usize) -> Option<Self> {
37 const RESERVED: usize = "_e{".len() + LEN_DIGITS + 1 + LEN_DIGITS + "}:".len() + 1 + "|d:".len() + TS_DIGITS;
38 let title = common::identifier_within(rng, budget.checked_sub(RESERVED)?);
39 if title.is_empty() {
40 return None;
41 }
42 let mut room = budget - RESERVED - title.len();
43 let count = OPT_COUNTS[rng.random_range(0..OPT_COUNTS.len())];
44 let mut options = Vec::new();
45 for _ in 0..count {
46 let prefix = OPT_PREFIXES[rng.random_range(0..OPT_PREFIXES.len())];
47 let Some(body_room) = room.checked_sub(1 + prefix.len()) else {
48 break;
49 };
50 let mut chunk = prefix.to_vec();
51 chunk.extend_from_slice(&common::optional_text_within(rng, body_room));
52 room -= 1 + chunk.len();
53 options.push(chunk);
54 }
55 Some(Self {
56 title,
57 tags: common::tags_within(rng, room),
58 options,
59 })
60 }
61
62 pub(crate) fn floor(&self) -> usize {
69 const HEADER: usize = "_e{".len() + LEN_DIGITS + 1 + LEN_DIGITS + "}:".len();
70 HEADER
71 + self.title.len()
72 + 1
73 + self.options.iter().map(|opt| 1 + opt.len()).sum::<usize>()
74 + "|d:".len()
75 + TS_DIGITS
76 + common::tags_len(&self.tags)
77 }
78
79 pub(crate) fn render_within(
82 &self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>, budget: usize,
83 ) -> Option<usize> {
84 let text_room = budget.checked_sub(self.floor())?;
85 let text = common::optional_text_within(rng, text_room);
86 let mut itoa = itoa::Buffer::new();
87 out.extend_from_slice(b"_e{");
88 out.extend_from_slice(itoa.format(self.title.len()).as_bytes());
89 out.push(b',');
90 out.extend_from_slice(itoa.format(text.len()).as_bytes());
91 out.extend_from_slice(b"}:");
92 out.extend_from_slice(&self.title);
93 out.push(b'|');
94 out.extend_from_slice(&text);
95 for opt in &self.options {
96 out.push(b'|');
97 out.extend_from_slice(opt);
98 }
99 out.extend_from_slice(b"|d:");
100 out.extend_from_slice(itoa.format(fresh_timestamp(rng)).as_bytes());
101 common::serialize_tags(&self.tags, out);
102 Some(0)
103 }
104
105 pub(crate) fn render(&self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>) -> usize {
106 let text = common::optional_text(rng);
107 let mut itoa = itoa::Buffer::new();
108 out.extend_from_slice(b"_e{");
109 out.extend_from_slice(itoa.format(self.title.len()).as_bytes());
110 out.push(b',');
111 out.extend_from_slice(itoa.format(text.len()).as_bytes());
112 out.extend_from_slice(b"}:");
113 out.extend_from_slice(&self.title);
114 out.push(b'|');
115 out.extend_from_slice(&text);
116 for opt in &self.options {
117 out.push(b'|');
118 out.extend_from_slice(opt);
119 }
120 out.extend_from_slice(b"|d:");
121 out.extend_from_slice(itoa.format(fresh_timestamp(rng)).as_bytes());
122 common::serialize_tags(&self.tags, out);
123 0
124 }
125
126 pub(crate) fn encode(&self, out: &mut Vec<u8>) {
128 put_bytes(out, &self.title);
129 put_tags(out, &self.tags);
130 put_tags(out, &self.options);
131 }
132
133 pub(crate) fn decode(buf: &[u8], pos: &mut usize) -> Option<Self> {
135 let title = get_bytes(buf, pos)?.to_vec();
136 let tags = get_tags(buf, pos)?;
137 let options = get_tags(buf, pos)?;
138 Some(Self { title, tags, options })
139 }
140}