use bacnet_services::who_is::DeviceInstanceRange;
use bacnet_types::error::Error;
pub(crate) fn parse_discover_range(
range: Option<&str>,
) -> Result<Option<DeviceInstanceRange>, Box<dyn std::error::Error>> {
let Some(r) = range else {
return Ok(None);
};
let Some((lo, hi)) = r.split_once('-') else {
return Err(format!("invalid range format: '{r}', expected 'low-high'").into());
};
let missing = match (lo.is_empty(), hi.is_empty()) {
(false, true) => Some("a low limit but no high limit"),
(true, false) => Some("a high limit but no low limit"),
_ => None,
};
if let Some(missing) = missing {
return Err(format!(
"invalid range '{r}': it has {missing}; give both, as 'low-high', or none"
)
.into());
}
let low = lo
.parse::<u32>()
.map_err(|_| format!("invalid range low: '{lo}'"))?;
let high = hi
.parse::<u32>()
.map_err(|_| format!("invalid range high: '{hi}'"))?;
if low > high {
return Err(format!("invalid range: low ({low}) > high ({high})").into());
}
let range = DeviceInstanceRange::new(low, high).map_err(|error| match error {
Error::OutOfRange(message) => format!("invalid range '{r}': {message}"),
other => other.to_string(),
})?;
Ok(Some(range))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_bounded_and_absent_ranges() {
assert_eq!(
parse_discover_range(Some("101-103")).unwrap(),
Some(DeviceInstanceRange::new(101, 103).unwrap())
);
assert_eq!(parse_discover_range(None).unwrap(), None);
}
#[test]
fn rejects_inverted_and_malformed_ranges() {
let inverted = parse_discover_range(Some("5-1")).unwrap_err().to_string();
assert!(inverted.contains("low (5) > high (1)"), "{inverted}");
let malformed = parse_discover_range(Some("12")).unwrap_err().to_string();
assert!(malformed.contains("expected 'low-high'"), "{malformed}");
}
#[test]
fn limits_stop_at_the_highest_instance() {
assert_eq!(
parse_discover_range(Some("0-4194303")).unwrap(),
Some(DeviceInstanceRange::new(0, 4_194_303).unwrap())
);
let past = parse_discover_range(Some("0-4194304"))
.unwrap_err()
.to_string();
assert!(
past.contains("high limit 4194304 is above the highest instance, 4194303"),
"{past}"
);
}
#[test]
fn rejects_a_range_with_one_limit() {
for (text, named) in [
("1000-", "a low limit but no high limit"),
("-2000", "a high limit but no low limit"),
] {
let error = parse_discover_range(Some(text)).unwrap_err().to_string();
assert!(
error.contains(named) && error.contains("give both"),
"{text}: {error}"
);
}
}
}