Skip to main content

vle_units/
parser.rs

1//! Parser for `"<value> <unit>"` strings.
2//!
3//! Examples:
4//! - `"25 degC"` → `(25.0, "degC")`
5//! - `"3.5 barg"` → `(3.5, "barg")`
6//! - `"-1.2e-3 kJ/kmol"` → `(-1.2e-3, "kJ/kmol")`
7
8use crate::registry::RegistryError;
9
10/// Split a string of the form `"<numeric value> <unit symbol>"` into its
11/// `(value, unit)` parts. Whitespace between the two is required.
12pub fn split_value_unit(s: &str) -> Result<(f64, &str), RegistryError> {
13    let trimmed = s.trim();
14    // Find first ASCII whitespace — value cannot contain spaces
15    let split_at = trimmed.find(char::is_whitespace).ok_or_else(|| {
16        RegistryError::ParseError(format!("expected '<value> <unit>', got '{s}'"))
17    })?;
18    let (val_str, rest) = trimmed.split_at(split_at);
19    let unit = rest.trim_start();
20    if unit.is_empty() {
21        return Err(RegistryError::ParseError(format!(
22            "missing unit after value in '{s}'"
23        )));
24    }
25    let value: f64 = val_str
26        .parse()
27        .map_err(|_| RegistryError::ParseError(format!("invalid number '{val_str}'")))?;
28    Ok((value, unit))
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn parses_simple() {
37        assert_eq!(split_value_unit("25 degC").unwrap(), (25.0, "degC"));
38        assert_eq!(split_value_unit("3.5 barg").unwrap(), (3.5, "barg"));
39        assert_eq!(
40            split_value_unit("-1.2e-3 kJ/kmol").unwrap(),
41            (-1.2e-3, "kJ/kmol")
42        );
43    }
44
45    #[test]
46    fn rejects_missing_unit() {
47        assert!(split_value_unit("25").is_err());
48        assert!(split_value_unit("25 ").is_err());
49    }
50
51    #[test]
52    fn rejects_bad_number() {
53        assert!(split_value_unit("abc kPa").is_err());
54    }
55}