1use std::{
8 fmt::{self, Display},
9 str::FromStr,
10 sync::LazyLock,
11};
12
13use regex::Regex;
14
15use crate::{impl_encoded_as, Error, UAString, UaNullable};
16
17#[derive(Debug)]
18pub struct NumericRangeError;
20
21impl fmt::Display for NumericRangeError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "NumericRangeError")
24 }
25}
26
27impl std::error::Error for NumericRangeError {}
28
29#[derive(Debug, Clone, PartialEq)]
56pub enum NumericRange {
57 None,
59 Index(u32),
61 Range(u32, u32),
63 MultipleRanges(Vec<NumericRange>),
65}
66
67impl NumericRange {
68 pub fn has_range(&self) -> bool {
70 !self.is_none()
71 }
72
73 pub fn is_none(&self) -> bool {
75 matches!(self, NumericRange::None)
76 }
77
78 fn byte_len(&self) -> usize {
79 4 + match self {
81 NumericRange::None => 0,
82 NumericRange::Index(i) => num_len(*i),
84 NumericRange::Range(l, r) => num_len(*l) + num_len(*r) + 1,
86 NumericRange::MultipleRanges(numeric_ranges) => {
87 numeric_ranges.iter().map(|r| r.byte_len()).sum::<usize>() + numeric_ranges.len()
88 - 1
89 }
90 }
91 }
92
93 fn from_ua_string(str: UAString) -> Result<Self, Error> {
94 str.as_ref()
95 .parse::<NumericRange>()
96 .map_err(Error::decoding)
97 }
98
99 fn to_ua_string(&self) -> Result<UAString, Error> {
100 match self {
101 NumericRange::None => Ok(UAString::null()),
102 _ => Ok(UAString::from(self.to_string())),
103 }
104 }
105}
106
107impl_encoded_as!(
108 NumericRange,
109 NumericRange::from_ua_string,
110 NumericRange::to_ua_string,
111 NumericRange::byte_len
112);
113
114fn num_len(n: u32) -> usize {
115 if n == 0 {
116 1
117 } else {
118 n.ilog10() as usize + 1
119 }
120}
121
122impl UaNullable for NumericRange {
123 fn is_ua_null(&self) -> bool {
124 self.is_none()
125 }
126}
127
128#[cfg(feature = "xml")]
129impl crate::xml::XmlType for NumericRange {
130 const TAG: &'static str = "NumericRange";
131}
132
133#[test]
135fn valid_numeric_ranges() {
136 let valid_ranges = vec![
137 ("", NumericRange::None, ""),
138 ("0", NumericRange::Index(0), "0"),
139 ("0000", NumericRange::Index(0), "0"),
140 ("1", NumericRange::Index(1), "1"),
141 ("0123456789", NumericRange::Index(123456789), "123456789"),
142 ("4294967295", NumericRange::Index(4294967295), "4294967295"),
143 ("1:2", NumericRange::Range(1, 2), "1:2"),
144 ("2:3", NumericRange::Range(2, 3), "2:3"),
145 (
146 "0:1,0:2,0:3,0:4,0:5",
147 NumericRange::MultipleRanges(vec![
148 NumericRange::Range(0, 1),
149 NumericRange::Range(0, 2),
150 NumericRange::Range(0, 3),
151 NumericRange::Range(0, 4),
152 NumericRange::Range(0, 5),
153 ]),
154 "0:1,0:2,0:3,0:4,0:5",
155 ),
156 (
157 "0:1,2,3,0:4,5,6,7,8,0:9",
158 NumericRange::MultipleRanges(vec![
159 NumericRange::Range(0, 1),
160 NumericRange::Index(2),
161 NumericRange::Index(3),
162 NumericRange::Range(0, 4),
163 NumericRange::Index(5),
164 NumericRange::Index(6),
165 NumericRange::Index(7),
166 NumericRange::Index(8),
167 NumericRange::Range(0, 9),
168 ]),
169 "0:1,2,3,0:4,5,6,7,8,0:9",
170 ),
171 ];
172 for vr in valid_ranges {
173 let range = vr.0.parse::<NumericRange>();
174 if range.is_err() {
175 println!("Range {} is in error when it should be ok", vr.0);
176 }
177 assert!(range.is_ok());
178 assert_eq!(range.unwrap(), vr.1);
179 assert_eq!(vr.2, &vr.1.to_string());
180 }
181}
182
183#[test]
184fn invalid_numeric_ranges() {
185 let invalid_ranges = vec![
188 " ",
189 " 1",
190 "1 ",
191 ":",
192 ":1",
193 "1:1",
194 "2:1",
195 "0:1,2,3,4:4",
196 "1:",
197 "1:1:2",
198 ",",
199 ":,",
200 ",:",
201 ",1",
202 "1,",
203 "1,2,",
204 "1,,2",
205 "01234567890",
206 "0,1,2,3,4,5,6,7,8,9,10",
207 "4294967296",
208 "0:4294967296",
209 "4294967296:0",
210 ];
211 for vr in invalid_ranges {
212 println!("vr = {vr}");
213 let range = vr.parse::<NumericRange>();
214 if range.is_ok() {
215 println!("Range {vr} is ok when it should be in error");
216 }
217 assert!(range.is_err());
218 }
219}
220
221const MAX_INDICES: usize = 10;
222
223impl FromStr for NumericRange {
224 type Err = NumericRangeError;
225 fn from_str(s: &str) -> Result<Self, Self::Err> {
226 if s.is_empty() {
227 Ok(NumericRange::None)
228 } else {
229 let parts: Vec<_> = s.split(',').collect();
236 match parts.len() {
237 1 => Self::parse_range(parts[0]),
238 2..=MAX_INDICES => {
239 let mut ranges = Vec::with_capacity(parts.len());
241 for p in &parts {
242 if let Ok(range) = Self::parse_range(p) {
243 ranges.push(range);
244 } else {
245 return Err(NumericRangeError);
246 }
247 }
248 Ok(NumericRange::MultipleRanges(ranges))
249 }
250 _ => Err(NumericRangeError),
252 }
253 }
254 }
255}
256
257impl Display for NumericRange {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 match self {
260 NumericRange::None => write!(f, ""),
261 NumericRange::Index(idx) => write!(f, "{idx}"),
262 NumericRange::Range(min, max) => write!(f, "{min}:{max}"),
263 NumericRange::MultipleRanges(vec) => {
264 let mut needs_comma = false;
265 for r in vec {
266 if needs_comma {
267 write!(f, ",")?;
268 }
269 needs_comma = true;
270 write!(f, "{r}")?;
271 }
272 Ok(())
273 }
274 }
275 }
276}
277
278impl NumericRange {
279 pub fn new(s: &str) -> Result<Self, NumericRangeError> {
281 Self::from_str(s)
282 }
283
284 fn parse_range(s: &str) -> Result<NumericRange, NumericRangeError> {
285 if s.is_empty() {
286 Err(NumericRangeError)
287 } else {
288 static RE: LazyLock<Regex> = LazyLock::new(|| {
296 Regex::new("^(?P<min>[0-9]{1,10})(:(?P<max>[0-9]{1,10}))?$").unwrap()
297 });
298 if let Some(captures) = RE.captures(s) {
299 let min = captures.name("min");
300 let max = captures.name("max");
301 match (min, max) {
302 (None, None) | (None, Some(_)) => Err(NumericRangeError),
303 (Some(min), None) => min
304 .as_str()
305 .parse::<u32>()
306 .map(NumericRange::Index)
307 .map_err(|_| NumericRangeError),
308 (Some(min), Some(max)) => {
309 if let Ok(min) = min.as_str().parse::<u64>() {
311 if let Ok(max) = max.as_str().parse::<u64>() {
312 if min >= max || max > u32::MAX as u64 {
313 Err(NumericRangeError)
314 } else {
315 Ok(NumericRange::Range(min as u32, max as u32))
316 }
317 } else {
318 Err(NumericRangeError)
319 }
320 } else {
321 Err(NumericRangeError)
322 }
323 }
324 }
325 } else {
326 Err(NumericRangeError)
327 }
328 }
329 }
330
331 pub fn is_valid(&self) -> bool {
334 match self {
335 NumericRange::None => true,
336 NumericRange::Index(_) => true,
337 NumericRange::Range(min, max) => min < max,
338 NumericRange::MultipleRanges(ref ranges) => {
339 let found_invalid = ranges.iter().any(|r| {
340 match r {
342 NumericRange::MultipleRanges(_) => true,
343 r => !r.is_valid(),
344 }
345 });
346 !found_invalid
347 }
348 }
349 }
350}