ngdp_bpsv/
value.rs

1//! BPSV value types with type-safe conversions
2
3use crate::error::{Error, Result};
4use crate::field_type::BpsvFieldType;
5use std::fmt;
6
7/// Represents a typed value in a BPSV document
8#[derive(Debug, Clone, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum BpsvValue {
11    /// String value
12    String(String),
13    /// Hexadecimal value (stored as lowercase string)
14    Hex(String),
15    /// Decimal integer value
16    Decimal(i64),
17    /// Empty/null value
18    Empty,
19}
20
21impl BpsvValue {
22    /// Parse a string value according to the specified field type
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// use ngdp_bpsv::{BpsvValue, BpsvFieldType};
28    ///
29    /// let string_val = BpsvValue::parse("hello", &BpsvFieldType::String(0))?;
30    /// assert_eq!(string_val, BpsvValue::String("hello".to_string()));
31    ///
32    /// let hex_val = BpsvValue::parse("ABCD1234", &BpsvFieldType::Hex(4))?;
33    /// assert_eq!(hex_val, BpsvValue::Hex("abcd1234".to_string()));
34    ///
35    /// let dec_val = BpsvValue::parse("1234", &BpsvFieldType::Decimal(4))?;
36    /// assert_eq!(dec_val, BpsvValue::Decimal(1234));
37    ///
38    /// let empty_val = BpsvValue::parse("", &BpsvFieldType::String(0))?;
39    /// assert_eq!(empty_val, BpsvValue::Empty);
40    /// # Ok::<(), ngdp_bpsv::Error>(())
41    /// ```
42    pub fn parse(value: &str, field_type: &BpsvFieldType) -> Result<Self> {
43        if value.is_empty() {
44            return Ok(Self::Empty);
45        }
46
47        match field_type {
48            BpsvFieldType::String(_) => {
49                if !field_type.is_valid_value(value) {
50                    return Err(Error::InvalidValue {
51                        field: "unknown".to_string(),
52                        field_type: field_type.to_string(),
53                        value: value.to_string(),
54                    });
55                }
56                Ok(Self::String(value.to_string()))
57            }
58            BpsvFieldType::Hex(_) => {
59                // This should not happen due to early return, but be defensive
60                if value.is_empty() {
61                    return Ok(Self::Empty);
62                }
63                if !field_type.is_valid_value(value) {
64                    return Err(Error::InvalidHex {
65                        value: value.to_string(),
66                    });
67                }
68                Ok(Self::Hex(value.to_lowercase()))
69            }
70            BpsvFieldType::Decimal(_) => {
71                // This should not happen due to early return, but be defensive
72                if value.is_empty() {
73                    return Ok(Self::Empty);
74                }
75                let num = value.parse::<i64>().map_err(|_| Error::InvalidNumber {
76                    value: value.to_string(),
77                })?;
78                Ok(Self::Decimal(num))
79            }
80        }
81    }
82
83    /// Convert the value to its string representation for BPSV output
84    pub fn to_bpsv_string(&self) -> String {
85        match self {
86            Self::String(s) => s.clone(),
87            Self::Hex(h) => h.clone(),
88            Self::Decimal(d) => d.to_string(),
89            Self::Empty => String::new(),
90        }
91    }
92
93    /// Check if this value is empty
94    pub fn is_empty(&self) -> bool {
95        matches!(self, Self::Empty)
96    }
97
98    /// Get the value as a string, if it is a string type
99    pub fn as_string(&self) -> Option<&str> {
100        match self {
101            Self::String(s) => Some(s),
102            _ => None,
103        }
104    }
105
106    /// Get the value as a hex string, if it is a hex type
107    pub fn as_hex(&self) -> Option<&str> {
108        match self {
109            Self::Hex(h) => Some(h),
110            _ => None,
111        }
112    }
113
114    /// Get the value as a decimal number, if it is a decimal type
115    pub fn as_decimal(&self) -> Option<i64> {
116        match self {
117            Self::Decimal(d) => Some(*d),
118            _ => None,
119        }
120    }
121
122    /// Convert to string value, consuming self
123    pub fn into_string(self) -> Option<String> {
124        match self {
125            Self::String(s) => Some(s),
126            _ => None,
127        }
128    }
129
130    /// Convert to hex value, consuming self
131    pub fn into_hex(self) -> Option<String> {
132        match self {
133            Self::Hex(h) => Some(h),
134            _ => None,
135        }
136    }
137
138    /// Convert to decimal value, consuming self
139    pub fn into_decimal(self) -> Option<i64> {
140        match self {
141            Self::Decimal(d) => Some(d),
142            _ => None,
143        }
144    }
145
146    /// Get the type of this value
147    pub fn value_type(&self) -> &'static str {
148        match self {
149            Self::String(_) => "String",
150            Self::Hex(_) => "Hex",
151            Self::Decimal(_) => "Decimal",
152            Self::Empty => "Empty",
153        }
154    }
155
156    /// Check if this value is compatible with the given field type
157    pub fn is_compatible_with(&self, field_type: &BpsvFieldType) -> bool {
158        match (self, field_type) {
159            (Self::String(_), BpsvFieldType::String(_)) => true,
160            (Self::Hex(_), BpsvFieldType::Hex(_)) => true,
161            (Self::Decimal(_), BpsvFieldType::Decimal(_)) => true,
162            (Self::Empty, _) => true, // Empty is compatible with any type
163            _ => false,
164        }
165    }
166}
167
168impl fmt::Display for BpsvValue {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "{}", self.to_bpsv_string())
171    }
172}
173
174impl From<String> for BpsvValue {
175    fn from(s: String) -> Self {
176        if s.is_empty() {
177            Self::Empty
178        } else {
179            Self::String(s)
180        }
181    }
182}
183
184impl From<&str> for BpsvValue {
185    fn from(s: &str) -> Self {
186        if s.is_empty() {
187            Self::Empty
188        } else {
189            Self::String(s.to_string())
190        }
191    }
192}
193
194impl From<i64> for BpsvValue {
195    fn from(i: i64) -> Self {
196        Self::Decimal(i)
197    }
198}
199
200impl From<i32> for BpsvValue {
201    fn from(i: i32) -> Self {
202        Self::Decimal(i64::from(i))
203    }
204}
205
206impl From<u32> for BpsvValue {
207    fn from(i: u32) -> Self {
208        Self::Decimal(i64::from(i))
209    }
210}
211
212impl From<u64> for BpsvValue {
213    fn from(i: u64) -> Self {
214        #[allow(clippy::cast_possible_wrap)]
215        Self::Decimal(i as i64)
216    }
217}
218
219impl TryFrom<BpsvValue> for String {
220    type Error = Error;
221
222    fn try_from(value: BpsvValue) -> Result<Self> {
223        match value {
224            BpsvValue::String(s) => Ok(s),
225            BpsvValue::Empty => Ok(String::new()),
226            _ => Err(Error::InvalidValue {
227                field: "unknown".to_string(),
228                field_type: "String".to_string(),
229                value: value.to_bpsv_string(),
230            }),
231        }
232    }
233}
234
235impl TryFrom<BpsvValue> for i64 {
236    type Error = Error;
237
238    fn try_from(value: BpsvValue) -> Result<Self> {
239        match value {
240            BpsvValue::Decimal(d) => Ok(d),
241            _ => Err(Error::InvalidValue {
242                field: "unknown".to_string(),
243                field_type: "Decimal".to_string(),
244                value: value.to_bpsv_string(),
245            }),
246        }
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_parse_values() {
256        let string_type = BpsvFieldType::String(0);
257        assert_eq!(
258            BpsvValue::parse("hello", &string_type).unwrap(),
259            BpsvValue::String("hello".to_string())
260        );
261
262        let hex_type = BpsvFieldType::Hex(4); // 4 bytes = 8 hex chars
263        assert_eq!(
264            BpsvValue::parse("ABCD1234", &hex_type).unwrap(),
265            BpsvValue::Hex("abcd1234".to_string())
266        );
267
268        let dec_type = BpsvFieldType::Decimal(4);
269        assert_eq!(
270            BpsvValue::parse("1234", &dec_type).unwrap(),
271            BpsvValue::Decimal(1234)
272        );
273
274        assert_eq!(
275            BpsvValue::parse("", &string_type).unwrap(),
276            BpsvValue::Empty
277        );
278    }
279
280    #[test]
281    fn test_invalid_values() {
282        let hex_type = BpsvFieldType::Hex(4);
283        assert!(BpsvValue::parse("xyz", &hex_type).is_err());
284
285        let dec_type = BpsvFieldType::Decimal(4);
286        assert!(BpsvValue::parse("abc", &dec_type).is_err());
287    }
288
289    #[test]
290    fn test_conversions() {
291        let string_val: BpsvValue = "hello".into();
292        assert_eq!(string_val, BpsvValue::String("hello".to_string()));
293
294        let num_val: BpsvValue = 1234i64.into();
295        assert_eq!(num_val, BpsvValue::Decimal(1234));
296
297        let empty_val: BpsvValue = "".into();
298        assert_eq!(empty_val, BpsvValue::Empty);
299    }
300
301    #[test]
302    fn test_accessors() {
303        let string_val = BpsvValue::String("hello".to_string());
304        assert_eq!(string_val.as_string(), Some("hello"));
305        assert_eq!(string_val.as_hex(), None);
306        assert_eq!(string_val.as_decimal(), None);
307
308        let hex_val = BpsvValue::Hex("abcd".to_string());
309        assert_eq!(hex_val.as_hex(), Some("abcd"));
310        assert_eq!(hex_val.as_string(), None);
311
312        let dec_val = BpsvValue::Decimal(1234);
313        assert_eq!(dec_val.as_decimal(), Some(1234));
314        assert_eq!(dec_val.as_string(), None);
315    }
316
317    #[test]
318    fn test_compatibility() {
319        let string_val = BpsvValue::String("hello".to_string());
320        let string_type = BpsvFieldType::String(0);
321        let hex_type = BpsvFieldType::Hex(4);
322
323        assert!(string_val.is_compatible_with(&string_type));
324        assert!(!string_val.is_compatible_with(&hex_type));
325
326        let empty_val = BpsvValue::Empty;
327        assert!(empty_val.is_compatible_with(&string_type));
328        assert!(empty_val.is_compatible_with(&hex_type));
329    }
330
331    #[test]
332    fn test_to_bpsv_string() {
333        assert_eq!(
334            BpsvValue::String("hello".to_string()).to_bpsv_string(),
335            "hello"
336        );
337        assert_eq!(BpsvValue::Hex("abcd".to_string()).to_bpsv_string(), "abcd");
338        assert_eq!(BpsvValue::Decimal(1234).to_bpsv_string(), "1234");
339        assert_eq!(BpsvValue::Empty.to_bpsv_string(), "");
340    }
341}