1use std::fmt;
2use std::str::FromStr;
3
4use anyhow::{Result, anyhow};
5use bon::Builder;
6use bstr::{BStr, BString, ByteSlice};
7
8#[derive(Debug, Clone)]
18pub enum AttributeValue {
19 Int(isize),
20 Float(f32),
21 String(BString),
22 Json(serde_json::Value),
23 Hex(BString),
24 Bytes(Vec<u8>),
25}
26
27#[derive(Debug, Clone, Builder, Default)]
42#[builder(on(BString, into))]
43pub struct Attribute {
44 pub tag: BString,
45 #[builder(default = 'Z')]
46 pub attribute_type: char,
47 pub value: BString,
48}
49
50impl FromStr for Attribute {
51 type Err = anyhow::Error;
52
53 fn from_str(s: &str) -> Result<Self> {
54 let parts: Vec<&str> = s.splitn(3, ':').collect();
56 if parts.len() < 3 {
57 return Err(anyhow!("Invalid attribute format: {}", s));
58 }
59
60 let tag = parts[0].into();
61 let attr_type = parts[1]
62 .chars()
63 .next()
64 .ok_or_else(|| anyhow!("Empty attribute type"))?;
65 let value = parts[2].into();
66
67 match attr_type {
69 'i' | 'f' | 'Z' | 'J' | 'H' | 'B' => (),
70 _ => return Err(anyhow!("Unknown attribute type: {}", attr_type)),
71 }
72
73 Ok(Attribute {
74 tag,
75 attribute_type: attr_type,
76 value,
77 })
78 }
79}
80
81impl Attribute {
82 pub fn typed_value(&self) -> Result<AttributeValue> {
84 match self.attribute_type {
85 'i' => Ok(AttributeValue::Int(self.as_int()?)),
86 'f' => Ok(AttributeValue::Float(self.as_float()?)),
87 'Z' => Ok(AttributeValue::String(self.value.clone())),
88 'J' => Ok(AttributeValue::Json(self.as_json()?)),
89 'H' => {
90 Ok(AttributeValue::Hex(self.value.clone()))
92 }
93 'B' => {
94 let bytes = self.value.to_vec();
96 Ok(AttributeValue::Bytes(bytes))
97 }
98 _ => Err(anyhow!(
99 "Unsupported attribute type: {}",
100 self.attribute_type
101 )),
102 }
103 }
104
105 pub fn as_int(&self) -> Result<isize> {
107 if self.attribute_type != 'i' {
108 return Err(anyhow!("Attribute is not an integer type"));
109 }
110 self.value
111 .to_str()?
112 .parse()
113 .map_err(|e| anyhow!("Failed to parse integer: {}", e))
114 }
115
116 pub fn as_float(&self) -> Result<f32> {
118 if self.attribute_type != 'f' {
119 return Err(anyhow!("Attribute is not a float type"));
120 }
121 self.value
122 .to_str()?
123 .parse()
124 .map_err(|e| anyhow!("Failed to parse float: {}", e))
125 }
126
127 pub fn as_string(&self) -> Result<&BStr> {
129 if self.attribute_type != 'Z' {
130 return Err(anyhow!("Attribute is not a string type"));
131 }
132 Ok(self.value.as_bstr())
133 }
134
135 pub fn as_json(&self) -> Result<serde_json::Value> {
137 if self.attribute_type != 'J' {
138 return Err(anyhow!("Attribute is not a JSON type"));
139 }
140 serde_json::from_str(self.value.to_str()?)
141 .map_err(|e| anyhow!("Failed to parse JSON: {}", e))
142 }
143}
144
145impl fmt::Display for Attribute {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}:{}:{}", self.tag, self.attribute_type, self.value)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::str::FromStr;
155
156 #[test]
157 fn test_attribute_from_str_valid() {
158 let attr = Attribute::from_str("ptc:i:1").unwrap();
159 assert_eq!(attr.tag, "ptc");
160 assert_eq!(attr.attribute_type, 'i');
161 assert_eq!(attr.value, "1");
162
163 let attr = Attribute::from_str("ptf:f:0.5").unwrap();
164 assert_eq!(attr.tag, "ptf");
165 assert_eq!(attr.attribute_type, 'f');
166 assert_eq!(attr.value, "0.5");
167
168 let attr = Attribute::from_str("name:Z:test_value").unwrap();
169 assert_eq!(attr.tag, "name");
170 assert_eq!(attr.attribute_type, 'Z');
171 assert_eq!(attr.value, "test_value");
172
173 let attr = Attribute::from_str("data:J:{\"key\":\"value\"}").unwrap();
174 assert_eq!(attr.tag, "data");
175 assert_eq!(attr.attribute_type, 'J');
176 assert_eq!(attr.value, "{\"key\":\"value\"}");
177 }
178
179 #[test]
180 fn test_attribute_from_str_invalid_format() {
181 let result = Attribute::from_str("ptc:i");
182 assert!(result.is_err());
183
184 let result = Attribute::from_str("ptc");
185 assert!(result.is_err());
186 }
187
188 #[test]
189 fn test_attribute_from_str_invalid_type() {
190 let result = Attribute::from_str("ptc:x:1");
191 assert!(result.is_err());
192 }
193
194 #[test]
195 fn test_attribute_as_int() {
196 let attr = Attribute {
197 tag: "ptc".into(),
198 attribute_type: 'i',
199 value: "42".into(),
200 };
201 assert_eq!(attr.as_int().unwrap(), 42);
202
203 let attr = Attribute {
204 tag: "ptc".into(),
205 attribute_type: 'f',
206 value: "42".into(),
207 };
208 assert!(attr.as_int().is_err());
209
210 let attr = Attribute {
211 tag: "ptc".into(),
212 attribute_type: 'i',
213 value: "not_a_number".into(),
214 };
215 assert!(attr.as_int().is_err());
216 }
217
218 #[test]
219 fn test_attribute_as_float() {
220 let attr = Attribute::builder()
221 .tag("ptf")
222 .attribute_type('f')
223 .value("3.1")
224 .build();
225 assert_eq!(attr.as_float().unwrap(), 3.1);
226
227 let attr = Attribute::builder()
228 .tag("ptf")
229 .attribute_type('i')
230 .value("3.14")
231 .build();
232 assert!(attr.as_float().is_err());
233
234 let attr = Attribute::builder()
235 .tag("ptf")
236 .attribute_type('f')
237 .value("not_a_number")
238 .build();
239 assert!(attr.as_float().is_err());
240 }
241
242 #[test]
243 fn test_attribute_as_string() {
244 let attr = Attribute {
245 tag: "name".into(),
246 attribute_type: 'Z',
247 value: "test_value".into(),
248 };
249 assert_eq!(attr.as_string().unwrap(), "test_value");
250
251 let attr = Attribute {
252 tag: "name".into(),
253 attribute_type: 'i',
254 value: "test_value".into(),
255 };
256 assert!(attr.as_string().is_err());
257 }
258
259 #[test]
260 fn test_attribute_as_json() {
261 let attr = Attribute::builder()
262 .tag("data")
263 .attribute_type('J')
264 .value("{\"key\":\"value\"}")
265 .build();
266
267 let json = attr.as_json().unwrap();
268 assert_eq!(json["key"], "value");
269
270 let attr = Attribute::builder()
271 .tag("data")
272 .attribute_type('Z')
273 .value("{\"key\":\"value\"}")
274 .build();
275 assert!(attr.as_json().is_err());
276
277 let attr = Attribute::builder()
278 .tag("data")
279 .attribute_type('J')
280 .value("invalid_json")
281 .build();
282 assert!(attr.as_json().is_err());
283 }
284
285 #[test]
286 fn test_attribute_display() {
287 let attr = Attribute::builder()
288 .tag("ptc")
289 .attribute_type('i')
290 .value("1")
291 .build();
292 assert_eq!(attr.to_string(), "ptc:i:1");
293
294 let attr = Attribute::builder()
295 .tag("ptf")
296 .attribute_type('f')
297 .value("0.5")
298 .build();
299 assert_eq!(attr.to_string(), "ptf:f:0.5");
300 }
301}