1use super::{Decode, DecodeError, Encode, EncodeError, TupleField};
2use core::hash::{Hash, Hasher};
3use std::convert::TryFrom;
4use std::fmt;
5use thiserror::Error;
6
7#[derive(Debug, Clone, Error, PartialEq, Eq)]
9pub enum TrackNamespaceError {
10 #[error("too many fields: {0} exceeds maximum of {1}")]
11 TooManyFields(usize, usize),
12
13 #[error("field too large: {0} bytes exceeds maximum of {1}")]
14 FieldTooLarge(usize, usize),
15}
16
17#[derive(Clone, Default, Eq, PartialEq)]
19pub struct TrackNamespace {
20 pub fields: Vec<TupleField>,
21}
22
23impl TrackNamespace {
24 pub const MAX_FIELDS: usize = 32;
25
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn add(&mut self, field: TupleField) {
31 self.fields.push(field);
32 }
33
34 pub fn clear(&mut self) {
35 self.fields.clear();
36 }
37
38 pub fn from_utf8_path(path: &str) -> Self {
39 let mut tuple = TrackNamespace::new();
40 for part in path.split('/') {
41 tuple.add(TupleField::from_utf8(part));
42 }
43 tuple
44 }
45
46 pub fn to_utf8_path(&self) -> String {
47 let mut path = String::new();
48 for field in &self.fields {
49 path.push('/');
50 path.push_str(&String::from_utf8_lossy(&field.value));
51 }
52 path
53 }
54}
55
56impl Hash for TrackNamespace {
57 fn hash<H: Hasher>(&self, state: &mut H) {
58 self.fields.hash(state);
59 }
60}
61
62impl Decode for TrackNamespace {
63 fn decode<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
64 let count = usize::decode(r)?;
65 if count > Self::MAX_FIELDS {
66 return Err(DecodeError::FieldBoundsExceeded(
67 "TrackNamespace tuples".to_string(),
68 ));
69 }
70
71 let mut fields = Vec::new();
72 for _ in 0..count {
73 fields.push(TupleField::decode(r)?);
74 }
75 Ok(Self { fields })
76 }
77}
78
79impl Encode for TrackNamespace {
80 fn encode<W: bytes::BufMut>(&self, w: &mut W) -> Result<(), EncodeError> {
81 if self.fields.len() > Self::MAX_FIELDS {
82 return Err(EncodeError::FieldBoundsExceeded(
83 "TrackNamespace tuples".to_string(),
84 ));
85 }
86 self.fields.len().encode(w)?;
87 for field in &self.fields {
88 field.encode(w)?;
89 }
90 Ok(())
91 }
92}
93
94impl fmt::Debug for TrackNamespace {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{self}")
98 }
99}
100
101impl fmt::Display for TrackNamespace {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "{0}", self.to_utf8_path())
104 }
105}
106
107impl TryFrom<Vec<TupleField>> for TrackNamespace {
108 type Error = TrackNamespaceError;
109
110 fn try_from(fields: Vec<TupleField>) -> Result<Self, Self::Error> {
111 if fields.len() > Self::MAX_FIELDS {
112 return Err(TrackNamespaceError::TooManyFields(
113 fields.len(),
114 Self::MAX_FIELDS,
115 ));
116 }
117 for field in &fields {
118 if field.value.len() > TupleField::MAX_VALUE_SIZE {
119 return Err(TrackNamespaceError::FieldTooLarge(
120 field.value.len(),
121 TupleField::MAX_VALUE_SIZE,
122 ));
123 }
124 }
125 Ok(Self { fields })
126 }
127}
128
129impl TryFrom<&str> for TrackNamespace {
130 type Error = TrackNamespaceError;
131
132 fn try_from(path: &str) -> Result<Self, Self::Error> {
133 let fields: Vec<TupleField> = path
134 .split('/')
135 .filter(|s| !s.is_empty())
136 .map(TupleField::from_utf8)
137 .collect();
138 Self::try_from(fields)
139 }
140}
141
142impl TryFrom<String> for TrackNamespace {
143 type Error = TrackNamespaceError;
144
145 fn try_from(path: String) -> Result<Self, Self::Error> {
146 Self::try_from(path.as_str())
147 }
148}
149
150impl TryFrom<Vec<&str>> for TrackNamespace {
151 type Error = TrackNamespaceError;
152
153 fn try_from(parts: Vec<&str>) -> Result<Self, Self::Error> {
154 let fields: Vec<TupleField> = parts.into_iter().map(TupleField::from_utf8).collect();
155 Self::try_from(fields)
156 }
157}
158
159impl TryFrom<Vec<String>> for TrackNamespace {
160 type Error = TrackNamespaceError;
161
162 fn try_from(parts: Vec<String>) -> Result<Self, Self::Error> {
163 let fields: Vec<TupleField> = parts.iter().map(|s| TupleField::from_utf8(s)).collect();
164 Self::try_from(fields)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use bytes::Bytes;
172 use bytes::BytesMut;
173 use std::convert::TryInto;
174
175 #[test]
176 fn encode_decode() {
177 let mut buf = BytesMut::new();
178
179 let t = TrackNamespace::from_utf8_path("test/path/to/resource");
180 t.encode(&mut buf).unwrap();
181 #[rustfmt::skip]
182 assert_eq!(
183 buf.to_vec(),
184 vec![
185 0x04, 0x04, 0x74, 0x65, 0x73, 0x74,
188 0x04, 0x70, 0x61, 0x74, 0x68,
190 0x02, 0x74, 0x6f,
192 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65
194 ]
195 );
196 let decoded = TrackNamespace::decode(&mut buf).unwrap();
197 assert_eq!(decoded, t);
198
199 let mut t = TrackNamespace::new();
201 t.add(TupleField::from_utf8("test"));
202 t.encode(&mut buf).unwrap();
203 assert_eq!(
204 buf.to_vec(),
205 vec![
206 0x01, 0x04, 0x74, 0x65, 0x73, 0x74
209 ]
210 );
211 let decoded = TrackNamespace::decode(&mut buf).unwrap();
212 assert_eq!(decoded, t);
213 }
214
215 #[test]
216 fn encode_too_large() {
217 let mut buf = BytesMut::new();
218
219 let mut t = TrackNamespace::new();
220 for i in 0..TrackNamespace::MAX_FIELDS + 1 {
221 t.add(TupleField::from_utf8(&format!("field{}", i)));
222 }
223
224 let encoded = t.encode(&mut buf);
225 assert!(matches!(
226 encoded.unwrap_err(),
227 EncodeError::FieldBoundsExceeded(_)
228 ));
229 }
230
231 #[test]
232 fn decode_too_large() {
233 let mut data: Vec<u8> = vec![0x00; 256]; data[0] = (TrackNamespace::MAX_FIELDS + 1) as u8; let mut buf: Bytes = data.into();
236 let decoded = TrackNamespace::decode(&mut buf);
237 assert!(matches!(
238 decoded.unwrap_err(),
239 DecodeError::FieldBoundsExceeded(_)
240 ));
241 }
242
243 #[test]
244 fn try_from_str() {
245 let ns: TrackNamespace = "test/path/to/resource".try_into().unwrap();
246 assert_eq!(ns.fields.len(), 4);
247 assert_eq!(ns.to_utf8_path(), "/test/path/to/resource");
248 }
249
250 #[test]
251 fn try_from_string() {
252 let path = String::from("test/path");
253 let ns: TrackNamespace = path.try_into().unwrap();
254 assert_eq!(ns.fields.len(), 2);
255 assert_eq!(ns.to_utf8_path(), "/test/path");
256 }
257
258 #[test]
259 fn try_from_vec_str() {
260 let parts = vec!["test", "path", "to", "resource"];
261 let ns: TrackNamespace = parts.try_into().unwrap();
262 assert_eq!(ns.fields.len(), 4);
263 assert_eq!(ns.to_utf8_path(), "/test/path/to/resource");
264 }
265
266 #[test]
267 fn try_from_vec_string() {
268 let parts = vec![String::from("test"), String::from("path")];
269 let ns: TrackNamespace = parts.try_into().unwrap();
270 assert_eq!(ns.fields.len(), 2);
271 assert_eq!(ns.to_utf8_path(), "/test/path");
272 }
273
274 #[test]
275 fn try_from_vec_tuple_field() {
276 let fields = vec![TupleField::from_utf8("test"), TupleField::from_utf8("path")];
277 let ns: TrackNamespace = fields.try_into().unwrap();
278 assert_eq!(ns.fields.len(), 2);
279 assert_eq!(ns.to_utf8_path(), "/test/path");
280 }
281
282 #[test]
283 fn try_from_too_many_fields() {
284 let mut fields = Vec::new();
285 for i in 0..TrackNamespace::MAX_FIELDS + 1 {
286 fields.push(TupleField::from_utf8(&format!("field{}", i)));
287 }
288 let result: Result<TrackNamespace, _> = fields.try_into();
289 assert!(matches!(
290 result.unwrap_err(),
291 TrackNamespaceError::TooManyFields(33, 32)
292 ));
293 }
294
295 #[test]
296 fn try_from_field_too_large() {
297 let large_value = "x".repeat(TupleField::MAX_VALUE_SIZE + 1);
298 let fields = vec![TupleField {
299 value: large_value.into_bytes(),
300 }];
301 let result: Result<TrackNamespace, _> = fields.try_into();
302 assert!(matches!(
303 result.unwrap_err(),
304 TrackNamespaceError::FieldTooLarge(4097, 4096)
305 ));
306 }
307}