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