Skip to main content

dimvexa_data/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! The unit table of **dimvexa**, compiled from audited TOML at build time.
3//!
4//! The source of truth is `data/units/*.toml` (inside this crate): each unit carries an exact rational factor,
5//! an exactness flag (`defined` or `measured`), a cited source and a note, and has at least
6//! one golden test vector in `data/vectors/`. `cargo xtask validate-data` enforces this.
7//!
8//! ```
9//! let reg = dimvexa_data::registry()?;
10//! let c = reg.conversion("psi", "Pa")?;
11//! assert!((c.apply(1.0) - 6894.757_293_168_361).abs() < 1e-9);
12//! # Ok::<(), dimvexa_core::Error>(())
13//! ```
14#![forbid(unsafe_code)]
15
16use dimvexa_core::{AmbiguousDef, Error, Exactness, Kind, PrefixMode, Registry, Shape, UnitDef};
17
18// The ohm sign U+2126 alias is intentionally not NFC-normalised (it is a distinct code point users paste).
19#[allow(
20    clippy::unreadable_literal,
21    clippy::too_many_lines,
22    clippy::unicode_not_nfc,
23    missing_docs
24)]
25mod generated {
26    use super::{AmbiguousDef, Exactness, Kind, PrefixMode, Shape, UnitDef};
27    include!(concat!(env!("OUT_DIR"), "/units_generated.rs"));
28}
29
30pub use generated::{AMBIGUOUS, DATA_HASH, DATA_VERSION, PACKS, UNITS};
31
32/// Builds a [`Registry`] from the compiled seed table.
33///
34/// # Errors
35///
36/// Any definition error of the table. A table that passes `cargo xtask validate-data`
37/// never fails here; the error type exists so that no panic is possible.
38pub fn registry() -> Result<Registry, Error> {
39    Registry::from_defs(UNITS, AMBIGUOUS)
40}
41
42/// A printable identifier of the table: `v<version>+<hash>`.
43#[must_use]
44pub fn data_version() -> String {
45    format!("v{DATA_VERSION}+{DATA_HASH}")
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn table_loads_and_is_reasonably_sized() {
54        let reg = registry().unwrap();
55        assert!(reg.len() >= 50, "{}", reg.len());
56        assert_eq!(reg.len(), UNITS.len());
57        assert!(DATA_HASH.starts_with("fnv1a64:"));
58        assert!(PACKS.contains(&"base") && PACKS.contains(&"common"));
59    }
60
61    #[test]
62    fn every_ambiguous_symbol_is_rejected_with_candidates() {
63        let reg = registry().unwrap();
64        for a in AMBIGUOUS {
65            let err = reg.parse(a.symbol).unwrap_err();
66            assert_eq!(err.code(), "ambiguous_symbol", "{}", a.symbol);
67            assert_eq!(err.candidates().len(), a.candidates.len() + a.planned.len());
68            for c in a.candidates {
69                assert!(
70                    reg.parse(c).is_ok(),
71                    "candidate {c} of {} must resolve",
72                    a.symbol
73                );
74            }
75        }
76        for sym in ["ton", "gal", "oz", "pt", "cal", "BTU", "mile", "ppm", "hp"] {
77            assert!(AMBIGUOUS.iter().any(|a| a.symbol == sym), "{sym}");
78        }
79    }
80}