prikk_object/
canonical.rs1use prikk_error::{PrikkError, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum WireType {
13 String = 1,
15 Bytes = 2,
17 U32 = 3,
19 U64 = 4,
21 Bool = 5,
23 Record = 6,
25}
26
27pub trait CanonicalEncode {
29 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()>;
31
32 fn to_canonical_bytes(&self) -> Result<Vec<u8>> {
34 let mut writer = CanonicalWriter::new();
35 self.encode_canonical(&mut writer)?;
36 Ok(writer.finish())
37 }
38}
39
40#[derive(Debug, Default)]
42pub struct CanonicalWriter {
43 bytes: Vec<u8>,
44 last_tag: Option<u16>,
45}
46
47impl CanonicalWriter {
48 #[must_use]
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 #[must_use]
56 pub fn finish(self) -> Vec<u8> {
57 self.bytes
58 }
59
60 pub fn field_string(&mut self, tag: u16, value: &str) -> Result<()> {
62 self.field_raw(tag, WireType::String, value.as_bytes())
63 }
64
65 pub fn field_string_opt(&mut self, tag: u16, value: Option<&str>) -> Result<()> {
67 if let Some(value) = value {
68 self.field_string(tag, value)?;
69 }
70 Ok(())
71 }
72
73 pub fn field_bytes(&mut self, tag: u16, value: &[u8]) -> Result<()> {
75 self.field_raw(tag, WireType::Bytes, value)
76 }
77
78 pub fn field_u32(&mut self, tag: u16, value: u32) -> Result<()> {
80 self.field_raw(tag, WireType::U32, &value.to_be_bytes())
81 }
82
83 pub fn field_u64(&mut self, tag: u16, value: u64) -> Result<()> {
85 self.field_raw(tag, WireType::U64, &value.to_be_bytes())
86 }
87
88 pub fn field_bool(&mut self, tag: u16, value: bool) -> Result<()> {
90 let encoded = if value { [1_u8] } else { [0_u8] };
91 self.field_raw(tag, WireType::Bool, &encoded)
92 }
93
94 pub fn field_record<T: CanonicalEncode>(&mut self, tag: u16, value: &T) -> Result<()> {
96 let mut nested = CanonicalWriter::new();
97 value.encode_canonical(&mut nested)?;
98 self.field_raw(tag, WireType::Record, &nested.finish())
99 }
100
101 pub fn repeated_record<T: CanonicalEncode>(&mut self, tag: u16, values: &[T]) -> Result<()> {
103 for value in values {
104 self.field_record(tag, value)?;
105 }
106 Ok(())
107 }
108
109 pub fn repeated_string(&mut self, tag: u16, values: &[String]) -> Result<()> {
111 for value in values {
112 self.field_string(tag, value)?;
113 }
114 Ok(())
115 }
116
117 pub fn repeated_object_id(&mut self, tag: u16, values: &[crate::ObjectId]) -> Result<()> {
119 for value in values {
120 self.field_bytes(tag, value.as_bytes())?;
121 }
122 Ok(())
123 }
124
125 pub fn field_raw(&mut self, tag: u16, wire_type: WireType, value: &[u8]) -> Result<()> {
127 if tag == 0 {
128 return Err(PrikkError::CanonicalEncoding(
129 "field tag 0 is reserved".to_string(),
130 ));
131 }
132 if let Some(last) = self.last_tag {
133 if tag < last {
134 return Err(PrikkError::CanonicalEncoding(format!(
135 "field tag order violation: {tag} after {last}"
136 )));
137 }
138 }
139 self.last_tag = Some(tag);
140 self.bytes.extend_from_slice(&tag.to_be_bytes());
141 self.bytes.push(wire_type as u8);
142 self.bytes
143 .extend_from_slice(&(value.len() as u64).to_be_bytes());
144 self.bytes.extend_from_slice(value);
145 Ok(())
146 }
147}
148
149#[must_use]
151pub fn is_strictly_sorted<T: Ord>(values: &[T]) -> bool {
152 values.windows(2).all(|pair| {
153 let mut items = pair.iter();
154 match (items.next(), items.next()) {
155 (Some(left), Some(right)) => left < right,
156 _ => true,
157 }
158 })
159}
160
161#[must_use]
163pub fn is_contiguous_op_seq(values: &[u32]) -> bool {
164 values
165 .iter()
166 .enumerate()
167 .all(|(idx, value)| *value as usize == idx + 1)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::CanonicalWriter;
173
174 #[test]
175 fn rejects_decreasing_tags() {
176 let mut writer = CanonicalWriter::new();
177 assert!(writer.field_u32(2, 1).is_ok());
178 assert!(writer.field_u32(1, 1).is_err());
179 }
180}