ngdp_bpsv/
field_type.rs

1//! BPSV field type definitions and parsing
2
3use crate::error::{Error, Result};
4use std::fmt;
5
6/// Represents a BPSV field type with its length specification
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum BpsvFieldType {
10    /// String field with maximum length (0 = unlimited)
11    String(u32),
12    /// Hexadecimal field with byte count (N bytes = N*2 hex characters)
13    Hex(u32),
14    /// Decimal number field with storage size in bytes (e.g., 4 = uint32)
15    Decimal(u32),
16}
17
18impl BpsvFieldType {
19    /// Parse a field type from a string like "STRING:0", "HEX:16", "DEC:4"
20    ///
21    /// Parsing is case-insensitive for the type name.
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use ngdp_bpsv::BpsvFieldType;
27    ///
28    /// assert_eq!(BpsvFieldType::parse("STRING:0")?, BpsvFieldType::String(0));
29    /// assert_eq!(BpsvFieldType::parse("string:0")?, BpsvFieldType::String(0));
30    /// assert_eq!(BpsvFieldType::parse("String:0")?, BpsvFieldType::String(0));
31    /// assert_eq!(BpsvFieldType::parse("HEX:16")?, BpsvFieldType::Hex(16));
32    /// assert_eq!(BpsvFieldType::parse("hex:16")?, BpsvFieldType::Hex(16));
33    /// assert_eq!(BpsvFieldType::parse("DEC:4")?, BpsvFieldType::Decimal(4));
34    /// assert_eq!(BpsvFieldType::parse("dec:4")?, BpsvFieldType::Decimal(4));
35    /// # Ok::<(), ngdp_bpsv::Error>(())
36    /// ```
37    pub fn parse(type_spec: &str) -> Result<Self> {
38        let parts: Vec<&str> = type_spec.split(':').collect();
39        if parts.len() != 2 {
40            return Err(Error::InvalidFieldType {
41                field_type: type_spec.to_string(),
42            });
43        }
44
45        let type_name = parts[0].to_uppercase();
46        let length_str = parts[1];
47
48        let length: u32 = length_str.parse().map_err(|_| Error::InvalidFieldType {
49            field_type: type_spec.to_string(),
50        })?;
51
52        match type_name.as_str() {
53            "STRING" => Ok(BpsvFieldType::String(length)),
54            "HEX" => Ok(BpsvFieldType::Hex(length)),
55            "DEC" | "DECIMAL" => Ok(BpsvFieldType::Decimal(length)),
56            _ => Err(Error::InvalidFieldType {
57                field_type: type_spec.to_string(),
58            }),
59        }
60    }
61
62    /// Get the type name (uppercase)
63    pub fn type_name(&self) -> &'static str {
64        match self {
65            BpsvFieldType::String(_) => "STRING",
66            BpsvFieldType::Hex(_) => "HEX",
67            BpsvFieldType::Decimal(_) => "DEC",
68        }
69    }
70
71    /// Get the length specification
72    pub fn length(&self) -> u32 {
73        match self {
74            BpsvFieldType::String(len) => *len,
75            BpsvFieldType::Hex(len) => *len,
76            BpsvFieldType::Decimal(len) => *len,
77        }
78    }
79
80    /// Check if a string value is valid for this field type
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use ngdp_bpsv::BpsvFieldType;
86    ///
87    /// let string_type = BpsvFieldType::String(5);
88    /// assert!(string_type.is_valid_value("hello"));
89    /// assert!(!string_type.is_valid_value("too_long")); // > 5 chars
90    ///
91    /// let hex_type = BpsvFieldType::Hex(4);  // 4 bytes = 8 hex chars
92    /// assert!(hex_type.is_valid_value("abcd1234"));
93    /// assert!(hex_type.is_valid_value("12345678"));
94    /// assert!(!hex_type.is_valid_value("xyz")); // invalid hex
95    /// assert!(!hex_type.is_valid_value("1234")); // wrong length (need 8 chars)
96    ///
97    /// let dec_type = BpsvFieldType::Decimal(4);
98    /// assert!(dec_type.is_valid_value("1234"));
99    /// assert!(dec_type.is_valid_value("0"));
100    /// assert!(!dec_type.is_valid_value("abc")); // not a number
101    /// # Ok::<(), ngdp_bpsv::Error>(())
102    /// ```
103    pub fn is_valid_value(&self, value: &str) -> bool {
104        match self {
105            BpsvFieldType::String(max_len) => *max_len == 0 || value.len() <= *max_len as usize,
106            BpsvFieldType::Hex(byte_count) => {
107                if value.is_empty() {
108                    return true; // Empty values are always valid
109                }
110                // HEX:N means N bytes, which is N*2 hex characters
111                if *byte_count > 0 && value.len() != (*byte_count as usize * 2) {
112                    return false;
113                }
114                value.chars().all(|c| c.is_ascii_hexdigit())
115            }
116            BpsvFieldType::Decimal(_) => value.is_empty() || value.parse::<i64>().is_ok(),
117        }
118    }
119
120    /// Validate and potentially normalize a value for this field type
121    ///
122    /// Returns the normalized value or an error if invalid.
123    pub fn validate_value(&self, value: &str) -> Result<String> {
124        if !self.is_valid_value(value) {
125            return Err(Error::InvalidValue {
126                field: "unknown".to_string(),
127                field_type: self.to_string(),
128                value: value.to_string(),
129            });
130        }
131
132        match self {
133            BpsvFieldType::String(_) => Ok(value.to_string()),
134            BpsvFieldType::Hex(_) => {
135                if value.is_empty() {
136                    return Ok(value.to_string()); // Keep empty values as-is
137                }
138                Ok(value.to_lowercase()) // Normalize to lowercase
139            }
140            BpsvFieldType::Decimal(_) => {
141                if value.is_empty() {
142                    return Ok(value.to_string()); // Keep empty values as-is
143                }
144                // Normalize number (remove leading zeros, etc.)
145                let num: i64 = value.parse().map_err(|_| Error::InvalidNumber {
146                    value: value.to_string(),
147                })?;
148                Ok(num.to_string())
149            }
150        }
151    }
152}
153
154impl fmt::Display for BpsvFieldType {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{}:{}", self.type_name(), self.length())
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_parse_field_types() {
166        assert_eq!(
167            BpsvFieldType::parse("STRING:0").unwrap(),
168            BpsvFieldType::String(0)
169        );
170        assert_eq!(
171            BpsvFieldType::parse("string:0").unwrap(),
172            BpsvFieldType::String(0)
173        );
174        assert_eq!(
175            BpsvFieldType::parse("String:0").unwrap(),
176            BpsvFieldType::String(0)
177        );
178        assert_eq!(
179            BpsvFieldType::parse("HEX:16").unwrap(),
180            BpsvFieldType::Hex(16)
181        );
182        assert_eq!(
183            BpsvFieldType::parse("hex:16").unwrap(),
184            BpsvFieldType::Hex(16)
185        );
186        assert_eq!(
187            BpsvFieldType::parse("DEC:4").unwrap(),
188            BpsvFieldType::Decimal(4)
189        );
190        assert_eq!(
191            BpsvFieldType::parse("dec:4").unwrap(),
192            BpsvFieldType::Decimal(4)
193        );
194        assert_eq!(
195            BpsvFieldType::parse("DECIMAL:4").unwrap(),
196            BpsvFieldType::Decimal(4)
197        );
198    }
199
200    #[test]
201    fn test_invalid_field_types() {
202        assert!(BpsvFieldType::parse("INVALID:0").is_err());
203        assert!(BpsvFieldType::parse("STRING").is_err());
204        assert!(BpsvFieldType::parse("STRING:abc").is_err());
205        assert!(BpsvFieldType::parse("").is_err());
206    }
207
208    #[test]
209    fn test_value_validation() {
210        let string_type = BpsvFieldType::String(5);
211        assert!(string_type.is_valid_value("hello"));
212        assert!(string_type.is_valid_value(""));
213        assert!(!string_type.is_valid_value("toolong"));
214
215        let string_unlimited = BpsvFieldType::String(0);
216        assert!(string_unlimited.is_valid_value("any length string here"));
217
218        let hex_type = BpsvFieldType::Hex(4); // 4 bytes = 8 hex chars
219        assert!(hex_type.is_valid_value("abcd1234"));
220        assert!(hex_type.is_valid_value("12345678"));
221        assert!(hex_type.is_valid_value("ABCD1234"));
222        assert!(!hex_type.is_valid_value("xyz12345")); // invalid hex
223        assert!(!hex_type.is_valid_value("1234")); // too short (4 chars, need 8)
224        assert!(!hex_type.is_valid_value("123456789")); // too long (9 chars)
225
226        let hex_unlimited = BpsvFieldType::Hex(0);
227        assert!(hex_unlimited.is_valid_value("abc123"));
228        assert!(hex_unlimited.is_valid_value(""));
229        assert!(!hex_unlimited.is_valid_value("xyz"));
230
231        let dec_type = BpsvFieldType::Decimal(4);
232        assert!(dec_type.is_valid_value("1234"));
233        assert!(dec_type.is_valid_value("0"));
234        assert!(dec_type.is_valid_value("-123"));
235        assert!(!dec_type.is_valid_value("abc"));
236        assert!(!dec_type.is_valid_value("12.34"));
237    }
238
239    #[test]
240    fn test_normalize_values() {
241        let hex_type = BpsvFieldType::Hex(4); // 4 bytes = 8 hex chars
242        assert_eq!(hex_type.validate_value("ABCD1234").unwrap(), "abcd1234");
243
244        let dec_type = BpsvFieldType::Decimal(4);
245        assert_eq!(dec_type.validate_value("0123").unwrap(), "123");
246        assert_eq!(dec_type.validate_value("-0042").unwrap(), "-42");
247    }
248
249    #[test]
250    fn test_display() {
251        assert_eq!(BpsvFieldType::String(0).to_string(), "STRING:0");
252        assert_eq!(BpsvFieldType::Hex(16).to_string(), "HEX:16");
253        assert_eq!(BpsvFieldType::Decimal(4).to_string(), "DEC:4");
254    }
255}