code_moniker_core/core/moniker/
view.rs1use super::encoding::{EncodingError, HEADER_FIXED_LEN, VERSION, is_uri_kind, read_u16};
4
5#[derive(Copy, Clone, Eq, PartialEq, Debug)]
6pub struct Segment<'a> {
7 pub kind: &'a [u8],
8 pub name: &'a [u8],
9}
10
11#[derive(Copy, Clone, Debug)]
12pub struct MonikerView<'a> {
13 bytes: &'a [u8],
14 project_off: usize,
15 project_len: usize,
16 segs_off: usize,
17}
18
19impl<'a> MonikerView<'a> {
20 pub fn from_encoded(bytes: &'a [u8]) -> Result<Self, EncodingError> {
21 if bytes.len() < HEADER_FIXED_LEN {
22 return Err(EncodingError::Truncated);
23 }
24 let version = bytes[0];
25 if version != VERSION {
26 return Err(EncodingError::UnknownVersion(version));
27 }
28 let project_len = read_u16(bytes, 1) as usize;
29 let project_off = 3;
30 let segs_off = project_off + project_len;
31 if bytes.len() < segs_off {
32 return Err(EncodingError::ProjectOverflow);
33 }
34 let project = &bytes[project_off..project_off + project_len];
35 if project.is_empty() {
36 return Err(EncodingError::EmptyProject);
37 }
38 std::str::from_utf8(project).map_err(|_| EncodingError::NonUtf8Project)?;
39 let mut cursor = segs_off;
40 while cursor < bytes.len() {
41 if bytes.len() < cursor + 2 {
42 return Err(EncodingError::SegmentOverflow);
43 }
44 let kind_len = read_u16(bytes, cursor) as usize;
45 let kind_start = cursor + 2;
46 cursor = kind_start + kind_len;
47 if bytes.len() < cursor {
48 return Err(EncodingError::SegmentOverflow);
49 }
50 let kind = &bytes[kind_start..cursor];
51 std::str::from_utf8(kind).map_err(|_| EncodingError::NonUtf8SegmentKind)?;
52 if !is_uri_kind(kind) {
53 return Err(EncodingError::InvalidSegmentKind);
54 }
55 if bytes.len() < cursor + 2 {
56 return Err(EncodingError::SegmentOverflow);
57 }
58 let name_len = read_u16(bytes, cursor) as usize;
59 let name_start = cursor + 2;
60 cursor = name_start + name_len;
61 if bytes.len() < cursor {
62 return Err(EncodingError::SegmentOverflow);
63 }
64 let name = &bytes[name_start..cursor];
65 std::str::from_utf8(name).map_err(|_| EncodingError::NonUtf8SegmentName)?;
66 }
67 Ok(Self {
68 bytes,
69 project_off,
70 project_len,
71 segs_off,
72 })
73 }
74
75 #[allow(clippy::missing_safety_doc)]
76 pub(crate) unsafe fn from_encoded_unchecked(bytes: &'a [u8]) -> Self {
77 debug_assert!(bytes.len() >= HEADER_FIXED_LEN && bytes[0] == VERSION);
78 let project_len = read_u16(bytes, 1) as usize;
79 let project_off = 3;
80 let segs_off = project_off + project_len;
81 Self {
82 bytes,
83 project_off,
84 project_len,
85 segs_off,
86 }
87 }
88
89 pub fn project(&self) -> &'a [u8] {
90 &self.bytes[self.project_off..self.project_off + self.project_len]
91 }
92
93 pub fn segment_count(&self) -> u16 {
94 self.segments().count() as u16
95 }
96
97 pub fn segments(&self) -> SegmentIter<'a> {
98 SegmentIter {
99 bytes: self.bytes,
100 cursor: self.segs_off,
101 }
102 }
103
104 pub fn as_encoded(&self) -> &'a [u8] {
105 self.bytes
106 }
107
108 pub fn is_ancestor_of(&self, other: &MonikerView<'_>) -> bool {
109 if self.project() != other.project() {
110 return false;
111 }
112 other.bytes.starts_with(self.bytes)
113 }
114}
115
116#[derive(Clone, Debug)]
117pub struct SegmentIter<'a> {
118 bytes: &'a [u8],
119 cursor: usize,
120}
121
122impl<'a> Iterator for SegmentIter<'a> {
123 type Item = Segment<'a>;
124
125 fn next(&mut self) -> Option<Self::Item> {
126 if self.cursor >= self.bytes.len() {
127 return None;
128 }
129 let kind_len = read_u16(self.bytes, self.cursor) as usize;
130 let kind_start = self.cursor + 2;
131 let kind = &self.bytes[kind_start..kind_start + kind_len];
132 let name_len_off = kind_start + kind_len;
133 let name_len = read_u16(self.bytes, name_len_off) as usize;
134 let name_start = name_len_off + 2;
135 let name = &self.bytes[name_start..name_start + name_len];
136 self.cursor = name_start + name_len;
137 Some(Segment { kind, name })
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn view_rejects_truncated_buffer() {
147 assert_eq!(
148 MonikerView::from_encoded(&[2, 0]).unwrap_err(),
149 EncodingError::Truncated
150 );
151 }
152
153 #[test]
154 fn view_rejects_unknown_version() {
155 let buf = [99, 0, 0];
156 assert_eq!(
157 MonikerView::from_encoded(&buf).unwrap_err(),
158 EncodingError::UnknownVersion(99)
159 );
160 }
161
162 #[test]
163 fn view_rejects_project_overflow() {
164 let buf: Vec<u8> = vec![2, 10, 0, 0];
165 assert_eq!(
166 MonikerView::from_encoded(&buf).unwrap_err(),
167 EncodingError::ProjectOverflow
168 );
169 }
170
171 #[test]
172 fn view_rejects_empty_project() {
173 let buf: Vec<u8> = vec![2, 0, 0];
174 assert_eq!(
175 MonikerView::from_encoded(&buf).unwrap_err(),
176 EncodingError::EmptyProject
177 );
178 }
179
180 #[test]
181 fn view_rejects_segment_overflow() {
182 let buf: Vec<u8> = vec![2, 3, 0, b'a', b'p', b'p', 5, 0];
183 assert_eq!(
184 MonikerView::from_encoded(&buf).unwrap_err(),
185 EncodingError::SegmentOverflow
186 );
187 }
188
189 #[test]
190 fn view_rejects_invalid_segment_kind() {
191 let buf: Vec<u8> = vec![2, 3, 0, b'a', b'p', b'p', 1, 0, b'1', 1, 0, b'x'];
192 assert_eq!(
193 MonikerView::from_encoded(&buf).unwrap_err(),
194 EncodingError::InvalidSegmentKind
195 );
196 }
197
198 #[test]
199 fn view_accepts_project_only() {
200 let buf: Vec<u8> = vec![2, 3, 0, b'a', b'p', b'p'];
201 let v = MonikerView::from_encoded(&buf).unwrap();
202 assert_eq!(v.project(), b"app");
203 assert_eq!(v.segment_count(), 0);
204 }
205}