krabka_log/
barrier_marker.rs1use {
4 crate::BARRIER_CONTROL_TYPE,
5 krabka_protocol::{
6 ProtocolError,
7 primitives::{
8 fixed::{get_i16, get_i64},
9 string_bytes::get_string_owned,
10 },
11 records::Record,
12 },
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct BarrierMarker {
18 pub group: String,
20 pub epoch: i64,
22 pub triggered_at: i64,
25}
26
27pub fn parse_barrier_marker(record: &Record) -> Result<BarrierMarker, ProtocolError> {
35 let key = record
36 .key
37 .as_deref()
38 .ok_or(ProtocolError::InvalidValue("barrier marker has no key"))?;
39 let mut cur = key;
40 if get_i16(&mut cur)? != 0 {
41 return Err(ProtocolError::InvalidValue(
42 "unsupported control record key version",
43 ));
44 }
45 if get_i16(&mut cur)? != BARRIER_CONTROL_TYPE {
46 return Err(ProtocolError::InvalidValue(
47 "control record is not a barrier marker",
48 ));
49 }
50
51 let value = record
52 .value
53 .as_deref()
54 .ok_or(ProtocolError::InvalidValue("barrier marker has no value"))?;
55 let mut cur = value;
56 if get_i16(&mut cur)? != 0 {
57 return Err(ProtocolError::InvalidValue(
58 "unsupported barrier marker version",
59 ));
60 }
61 let group = get_string_owned(&mut cur)?;
62 let epoch = get_i64(&mut cur)?;
63 let triggered_at = get_i64(&mut cur)?;
64 if !cur.is_empty() {
65 return Err(ProtocolError::InvalidValue(
66 "trailing bytes after barrier marker",
67 ));
68 }
69 Ok(BarrierMarker {
70 group,
71 epoch,
72 triggered_at,
73 })
74}
75
76#[cfg(test)]
77mod tests {
78 use {super::*, assert2::assert, bytes::Bytes, krabka_protocol::records::Record};
79
80 fn record(group: &str, epoch: i64, triggered_at: i64) -> Record {
81 let mut key = Vec::with_capacity(4);
82 key.extend_from_slice(&0i16.to_be_bytes());
83 key.extend_from_slice(&BARRIER_CONTROL_TYPE.to_be_bytes());
84
85 let mut value = Vec::with_capacity(20 + group.len());
86 value.extend_from_slice(&0i16.to_be_bytes());
87 value.extend_from_slice(
88 &i16::try_from(group.len())
89 .expect("test group length fits marker")
90 .to_be_bytes(),
91 );
92 value.extend_from_slice(group.as_bytes());
93 value.extend_from_slice(&epoch.to_be_bytes());
94 value.extend_from_slice(&triggered_at.to_be_bytes());
95 Record {
96 key: Some(Bytes::from(key)),
97 value: Some(Bytes::from(value)),
98 ..Record::default()
99 }
100 }
101
102 #[test]
103 fn reads_a_barrier_marker_from_its_control_record() {
104 let marker = parse_barrier_marker(&record("orders-cut", 7, 1_724_500_000_000))
105 .expect("valid marker");
106 assert!(
107 marker
108 == BarrierMarker {
109 group: "orders-cut".to_owned(),
110 epoch: 7,
111 triggered_at: 1_724_500_000_000,
112 }
113 );
114 }
115
116 #[test]
117 fn refuses_a_record_without_a_key() {
118 let error = parse_barrier_marker(&Record::default()).expect_err("key is required");
119 assert!(matches!(
120 error,
121 ProtocolError::InvalidValue("barrier marker has no key")
122 ));
123 }
124}