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#![doc(
16    html_logo_url = "https://raw.githubusercontent.com/welworx/dimvexa/main/docs/brand/icon-256.png",
17    html_favicon_url = "https://raw.githubusercontent.com/welworx/dimvexa/main/docs/brand/icon-32.png"
18)]
19
20use dimvexa_core::{AmbiguousDef, Error, Exactness, Kind, PrefixMode, Registry, Shape, UnitDef};
21
22// The ohm sign U+2126 alias is intentionally not NFC-normalised (it is a distinct code point users paste).
23#[allow(
24    clippy::unreadable_literal,
25    clippy::too_many_lines,
26    clippy::unicode_not_nfc,
27    missing_docs
28)]
29mod generated {
30    use super::{AmbiguousDef, Exactness, Kind, PrefixMode, Shape, UnitDef};
31    include!(concat!(env!("OUT_DIR"), "/units_generated.rs"));
32}
33
34pub use generated::{AMBIGUOUS, DATA_HASH, DATA_VERSION, PACKS, UNITS};
35
36/// Builds a [`Registry`] from the compiled seed table.
37///
38/// # Errors
39///
40/// Any definition error of the table. A table that passes `cargo xtask validate-data`
41/// never fails here; the error type exists so that no panic is possible.
42pub fn registry() -> Result<Registry, Error> {
43    Registry::from_defs(UNITS, AMBIGUOUS)
44}
45
46/// A printable identifier of the table: `v<version>+<hash>`.
47#[must_use]
48pub fn data_version() -> String {
49    format!("v{DATA_VERSION}+{DATA_HASH}")
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn table_loads_and_is_reasonably_sized() {
58        let reg = registry().unwrap();
59        assert!(reg.len() >= 50, "{}", reg.len());
60        assert_eq!(reg.len(), UNITS.len());
61        assert!(DATA_HASH.starts_with("fnv1a64:"));
62        assert!(PACKS.contains(&"base") && PACKS.contains(&"common"));
63    }
64
65    #[test]
66    fn every_ambiguous_symbol_is_rejected_with_candidates() {
67        let reg = registry().unwrap();
68        for a in AMBIGUOUS {
69            let err = reg.parse(a.symbol).unwrap_err();
70            assert_eq!(err.code(), "ambiguous_symbol", "{}", a.symbol);
71            assert_eq!(err.candidates().len(), a.candidates.len() + a.planned.len());
72            for c in a.candidates {
73                assert!(
74                    reg.parse(c).is_ok(),
75                    "candidate {c} of {} must resolve",
76                    a.symbol
77                );
78            }
79        }
80        for sym in ["ton", "gal", "oz", "pt", "cal", "BTU", "mile", "ppm", "hp"] {
81            assert!(AMBIGUOUS.iter().any(|a| a.symbol == sym), "{sym}");
82        }
83    }
84}