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