#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum UnitDomain {
Chemistry,
Finance,
Measure,
Physics,
Time,
Wave,
}
impl UnitDomain {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Chemistry => "chemistry",
Self::Finance => "finance",
Self::Measure => "measure",
Self::Physics => "physics",
Self::Time => "time",
Self::Wave => "wave",
}
}
}
pub const SUPPORTED_DOMAINS: [UnitDomain; 6] = [
UnitDomain::Chemistry,
UnitDomain::Physics,
UnitDomain::Wave,
UnitDomain::Time,
UnitDomain::Finance,
UnitDomain::Measure,
];
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UnitSpec {
pub domain: UnitDomain,
pub quantity: &'static str,
pub symbol: &'static str,
pub scale_to_base: f64,
}
impl UnitSpec {
#[must_use]
pub const fn new(
domain: UnitDomain,
quantity: &'static str,
symbol: &'static str,
scale_to_base: f64,
) -> Self {
Self {
domain,
quantity,
symbol,
scale_to_base,
}
}
#[must_use]
pub fn is_compatible_with(self, other: Self) -> bool {
self.domain == other.domain && self.quantity == other.quantity
}
#[must_use]
pub fn convert_value(self, value: f64, target: Self) -> Option<f64> {
if !self.is_compatible_with(target) {
return None;
}
Some(value * self.scale_to_base / target.scale_to_base)
}
}
pub mod prelude {
pub use super::{SUPPORTED_DOMAINS, UnitDomain, UnitSpec};
}
#[cfg(test)]
mod tests {
use super::{SUPPORTED_DOMAINS, UnitDomain, UnitSpec};
#[test]
fn reports_supported_domains_in_expected_order() {
let labels: Vec<_> = SUPPORTED_DOMAINS
.iter()
.map(|domain| domain.as_str())
.collect();
assert_eq!(
labels,
vec!["chemistry", "physics", "wave", "time", "finance", "measure"]
);
}
#[test]
fn converts_compatible_linear_units() {
let meter = UnitSpec::new(UnitDomain::Measure, "length", "m", 1.0);
let kilometer = UnitSpec::new(UnitDomain::Measure, "length", "km", 1_000.0);
assert_eq!(kilometer.convert_value(2.5, meter), Some(2_500.0));
assert_eq!(meter.convert_value(750.0, kilometer), Some(0.75));
}
#[test]
fn rejects_incompatible_unit_conversions() {
let meter = UnitSpec::new(UnitDomain::Measure, "length", "m", 1.0);
let second = UnitSpec::new(UnitDomain::Time, "duration", "s", 1.0);
assert_eq!(meter.convert_value(1.0, second), None);
assert!(!meter.is_compatible_with(second));
}
}