lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 4e426dfbaa0bb44e59cd4b896d1a9662522e19a5
parent 5a99e7c85154a3c5d19b5f2e4e80f8c84dde31c8
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 14:01:18 +0000

core: rename public values without crate prefixes

- expose concise canonical core value names
- retain deprecated constructor-compatible consumer aliases
- regenerate DTO and successor source authorities
- verify no-std wasm and hermetic workspace gates

Diffstat:
Mcrates/core/src/currency.rs | 35++++++++++++++++++-----------------
Mcrates/core/src/decimal.rs | 65++++++++++++++++++++++++++++++++++-------------------------------
Mcrates/core/src/discount.rs | 6+++---
Mcrates/core/src/generated/dto_roots.rs | 14++++++--------
Mcrates/core/src/lib.rs | 32+++++++++++++++++++++++---------
Mcrates/core/src/money.rs | 47+++++++++++++++++++++++++----------------------
Mcrates/core/src/percent.rs | 35+++++++++++++++++++----------------
Mcrates/core/src/quantity.rs | 80+++++++++++++++++++++++++++++++++++++------------------------------------------
Mcrates/core/src/quantity_price.rs | 95+++++++++++++++++++++++++++++++++++--------------------------------------------
Mcrates/core/src/serde_ext.rs | 13++++---------
Mcrates/core/src/unit.rs | 151++++++++++++++++++++++++++++++++++++-------------------------------------------
Mcrates/core/tests/common/mod.rs | 25+++++++++++--------------
Mcrates/core/tests/currency.rs | 42+++++++++++++++++++++---------------------
Mcrates/core/tests/decimal.rs | 24++++++++++++------------
Mcrates/core/tests/discount.rs | 12++++++------
Mcrates/core/tests/money.rs | 88++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcrates/core/tests/package_boundary.rs | 34+++++++++++++++++++++++++++++++++-
Mcrates/core/tests/percent.rs | 16++++++++--------
Mcrates/core/tests/quantity.rs | 72+++++++++++++++++++++++++++++++++++-------------------------------------
Mcrates/core/tests/quantity_price.rs | 159+++++++++++++++++++++++++------------------------------------------------------
Mcrates/core/tests/serde.rs | 21+++++++++------------
Mcrates/core/tests/unit.rs | 120+++++++++++++++++++++++++++++++------------------------------------------------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.json | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.sha256 | 2+-
Mcrates/event_store/src/generated/source_maintenance_manifest.rs | 6+++---
Mtools/xtask/src/contract.rs | 41++++++++++++++++++-----------------------
Mtools/xtask/src/contract/food_availability_projection.rs | 20+++++---------------
Mtools/xtask/src/contract/source_maintenance.rs | 40++++++++++++++++++++++++++++++++++++++++
28 files changed, 678 insertions(+), 678 deletions(-)

diff --git a/crates/core/src/currency.rs b/crates/core/src/currency.rs @@ -13,9 +13,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeErr #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string"))] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RadrootsCoreCurrency([u8; 3]); +pub struct Currency([u8; 3]); -impl RadrootsCoreCurrency { +impl Currency { #[inline] pub const fn from_const(bytes: [u8; 3]) -> Result<Self, RadrootsCoreCurrencyParseError> { if Self::is_ascii_upper(bytes[0]) @@ -46,12 +46,12 @@ impl RadrootsCoreCurrency { core::str::from_utf8(&self.0).unwrap_or("???") } - pub const USD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"USD"); - pub const EUR: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"EUR"); - pub const GBP: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"GBP"); - pub const JPY: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"JPY"); - pub const CAD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"CAD"); - pub const AUD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"AUD"); + pub const USD: Currency = Currency(*b"USD"); + pub const EUR: Currency = Currency(*b"EUR"); + pub const GBP: Currency = Currency(*b"GBP"); + pub const JPY: Currency = Currency(*b"JPY"); + pub const CAD: Currency = Currency(*b"CAD"); + pub const AUD: Currency = Currency(*b"AUD"); #[inline] pub const fn minor_unit_exponent(&self) -> u32 { @@ -69,28 +69,26 @@ impl RadrootsCoreCurrency { } } -impl fmt::Debug for RadrootsCoreCurrency { +impl fmt::Debug for Currency { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("RadrootsCoreCurrency") - .field(&self.as_str()) - .finish() + f.debug_tuple("Currency").field(&self.as_str()).finish() } } -impl fmt::Display for RadrootsCoreCurrency { +impl fmt::Display for Currency { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } -impl TryFrom<&str> for RadrootsCoreCurrency { +impl TryFrom<&str> for Currency { type Error = RadrootsCoreCurrencyParseError; fn try_from(s: &str) -> Result<Self, Self::Error> { s.parse() } } -impl FromStr for RadrootsCoreCurrency { +impl FromStr for Currency { type Err = RadrootsCoreCurrencyParseError; fn from_str(s: &str) -> Result<Self, Self::Err> { @@ -123,16 +121,19 @@ impl fmt::Display for RadrootsCoreCurrencyParseError { impl std::error::Error for RadrootsCoreCurrencyParseError {} #[cfg(feature = "serde")] -impl Serialize for RadrootsCoreCurrency { +impl Serialize for Currency { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_str(self.as_str()) } } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for RadrootsCoreCurrency { +impl<'de> Deserialize<'de> for Currency { fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { let s = String::deserialize(de)?; s.parse().map_err(D::Error::custom) } } + +#[deprecated(since = "0.1.0", note = "renamed to `Currency`")] +pub use self::Currency as RadrootsCoreCurrency; diff --git a/crates/core/src/decimal.rs b/crates/core/src/decimal.rs @@ -1,7 +1,7 @@ use core::fmt; use core::ops::{Add, Div, Mul, Sub}; use core::str::FromStr; -use rust_decimal::Decimal; +use rust_decimal::Decimal as RustDecimal; use rust_decimal::prelude::ToPrimitive; #[cfg(all(feature = "serde", not(feature = "std")))] @@ -15,11 +15,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeErr #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string"))] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct RadrootsCoreDecimal(pub Decimal); +pub struct Decimal(pub RustDecimal); -impl RadrootsCoreDecimal { - pub const ZERO: Self = Self(Decimal::ZERO); - pub const ONE: Self = Self(Decimal::ONE); +impl Decimal { + pub const ZERO: Self = Self(RustDecimal::ZERO); + pub const ONE: Self = Self(RustDecimal::ONE); #[inline] pub fn is_zero(&self) -> bool { @@ -34,7 +34,7 @@ impl RadrootsCoreDecimal { self.0.rescale(scale); } #[inline] - pub fn normalize(&self) -> Decimal { + pub fn normalize(&self) -> RustDecimal { self.0.normalize() } @@ -45,13 +45,13 @@ impl RadrootsCoreDecimal { #[inline] pub fn from_str_exact(s: &str) -> Result<Self, rust_decimal::Error> { - Decimal::from_str_exact(s).map(Self) + RustDecimal::from_str_exact(s).map(Self) } #[inline] pub fn from_f64_display(n: f64) -> Result<Self, rust_decimal::Error> { let s = format!("{:.17}", n); - Decimal::from_str(&s).map(Self) + RustDecimal::from_str(&s).map(Self) } #[inline] pub fn to_f64_lossy(&self) -> Option<f64> { @@ -69,87 +69,90 @@ impl RadrootsCoreDecimal { } #[cfg(feature = "serde")] -impl Serialize for RadrootsCoreDecimal { +impl Serialize for Decimal { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { serializer.serialize_str(&self.normalize().to_string()) } } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for RadrootsCoreDecimal { +impl<'de> Deserialize<'de> for Decimal { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { let s = String::deserialize(deserializer)?; - Decimal::from_str(&s) - .map(RadrootsCoreDecimal) + RustDecimal::from_str(&s) + .map(Decimal) .map_err(D::Error::custom) } } -impl fmt::Display for RadrootsCoreDecimal { +impl fmt::Display for Decimal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.normalize().to_string()) } } -impl From<Decimal> for RadrootsCoreDecimal { - fn from(d: Decimal) -> Self { +impl From<RustDecimal> for Decimal { + fn from(d: RustDecimal) -> Self { Self(d) } } -impl From<RadrootsCoreDecimal> for Decimal { - fn from(d: RadrootsCoreDecimal) -> Self { +impl From<Decimal> for RustDecimal { + fn from(d: Decimal) -> Self { d.0 } } -impl From<u32> for RadrootsCoreDecimal { +impl From<u32> for Decimal { fn from(v: u32) -> Self { - Self(Decimal::from(v)) + Self(RustDecimal::from(v)) } } -impl From<i32> for RadrootsCoreDecimal { +impl From<i32> for Decimal { fn from(v: i32) -> Self { - Self(Decimal::from(v)) + Self(RustDecimal::from(v)) } } -impl From<u64> for RadrootsCoreDecimal { +impl From<u64> for Decimal { fn from(v: u64) -> Self { - Self(Decimal::from(v)) + Self(RustDecimal::from(v)) } } -impl From<i64> for RadrootsCoreDecimal { +impl From<i64> for Decimal { fn from(v: i64) -> Self { - Self(Decimal::from(v)) + Self(RustDecimal::from(v)) } } -impl Add for RadrootsCoreDecimal { +impl Add for Decimal { type Output = Self; fn add(self, rhs: Self) -> Self { Self(self.0 + rhs.0) } } -impl Sub for RadrootsCoreDecimal { +impl Sub for Decimal { type Output = Self; fn sub(self, rhs: Self) -> Self { Self(self.0 - rhs.0) } } -impl Mul for RadrootsCoreDecimal { +impl Mul for Decimal { type Output = Self; fn mul(self, rhs: Self) -> Self { Self(self.0 * rhs.0) } } -impl Div for RadrootsCoreDecimal { +impl Div for Decimal { type Output = Self; fn div(self, rhs: Self) -> Self { Self(self.0 / rhs.0) } } -impl FromStr for RadrootsCoreDecimal { +impl FromStr for Decimal { type Err = rust_decimal::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { - Decimal::from_str(s).map(RadrootsCoreDecimal) + RustDecimal::from_str(s).map(Decimal) } } + +#[deprecated(since = "0.1.0", note = "renamed to `Decimal`")] +pub use self::Decimal as RadrootsCoreDecimal; diff --git a/crates/core/src/discount.rs b/crates/core/src/discount.rs @@ -23,7 +23,7 @@ pub enum RadrootsCoreDiscountScope { )] pub enum RadrootsCoreDiscountThreshold { BinCount { bin_id: String, min: u32 }, - OrderQuantity { min: crate::RadrootsCoreQuantity }, + OrderQuantity { min: crate::Quantity }, } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -35,8 +35,8 @@ pub enum RadrootsCoreDiscountThreshold { serde(rename_all = "snake_case", tag = "kind", content = "amount") )] pub enum RadrootsCoreDiscountValue { - MoneyPerBin(crate::RadrootsCoreMoney), - Percent(crate::RadrootsCorePercent), + MoneyPerBin(crate::Money), + Percent(crate::Percent), } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/core/src/generated/dto_roots.rs b/crates/core/src/generated/dto_roots.rs @@ -8,13 +8,11 @@ pub fn dto_bindgen_roots() -> ::std::vec::Vec<::dto_bindgen::export::RootDescrip ::dto_bindgen::export::RootDescriptor::new::<crate::discount::RadrootsCoreDiscountThreshold>( ), ::dto_bindgen::export::RootDescriptor::new::<crate::discount::RadrootsCoreDiscountValue>(), - ::dto_bindgen::export::RootDescriptor::new::<crate::money::RadrootsCoreMoney>(), - ::dto_bindgen::export::RootDescriptor::new::<crate::percent::RadrootsCorePercent>(), - ::dto_bindgen::export::RootDescriptor::new::<crate::quantity::RadrootsCoreQuantity>(), - ::dto_bindgen::export::RootDescriptor::new::< - crate::quantity_price::RadrootsCoreQuantityPrice, - >(), - ::dto_bindgen::export::RootDescriptor::new::<crate::unit::RadrootsCoreUnit>(), - ::dto_bindgen::export::RootDescriptor::new::<crate::unit::RadrootsCoreUnitDimension>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::money::Money>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::percent::Percent>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::quantity::Quantity>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::quantity_price::QuantityPrice>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::unit::Unit>(), + ::dto_bindgen::export::RootDescriptor::new::<crate::unit::UnitDimension>(), ] } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs @@ -17,20 +17,34 @@ pub mod quantity_price; pub mod serde_ext; pub mod unit; -pub use currency::{RadrootsCoreCurrency, RadrootsCoreCurrencyParseError}; -pub use decimal::RadrootsCoreDecimal; +pub use currency::{Currency, RadrootsCoreCurrencyParseError}; +pub use decimal::Decimal; pub use discount::{ RadrootsCoreDiscount, RadrootsCoreDiscountScope, RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, }; -pub use money::{RadrootsCoreMoney, RadrootsCoreMoneyInvariantError}; -pub use percent::{RadrootsCorePercent, RadrootsCorePercentParseError}; -pub use quantity::{RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError}; +pub use money::{Money, RadrootsCoreMoneyInvariantError}; +pub use percent::{Percent, RadrootsCorePercentParseError}; +pub use quantity::{Quantity, RadrootsCoreQuantityInvariantError}; pub use quantity_price::{ - RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps, + QuantityPrice, RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps, }; pub use unit::{ - RadrootsCoreUnit, RadrootsCoreUnitConvertError, RadrootsCoreUnitDimension, - RadrootsCoreUnitParseError, convert_mass_decimal, convert_unit_decimal, convert_volume_decimal, - parse_mass_unit, parse_volume_unit, + RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError, Unit, convert_mass_decimal, + convert_unit_decimal, convert_volume_decimal, parse_mass_unit, parse_volume_unit, }; + +#[allow(deprecated)] +pub use currency::RadrootsCoreCurrency; +#[allow(deprecated)] +pub use decimal::RadrootsCoreDecimal; +#[allow(deprecated)] +pub use money::RadrootsCoreMoney; +#[allow(deprecated)] +pub use percent::RadrootsCorePercent; +#[allow(deprecated)] +pub use quantity::RadrootsCoreQuantity; +#[allow(deprecated)] +pub use quantity_price::RadrootsCoreQuantityPrice; +#[allow(deprecated)] +pub use unit::{RadrootsCoreUnit, RadrootsCoreUnitDimension}; diff --git a/crates/core/src/money.rs b/crates/core/src/money.rs @@ -7,9 +7,9 @@ use rust_decimal::prelude::ToPrimitive; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsCoreMoney { - pub amount: crate::RadrootsCoreDecimal, - pub currency: crate::RadrootsCoreCurrency, +pub struct Money { + pub amount: crate::Decimal, + pub currency: crate::Currency, } #[non_exhaustive] @@ -35,16 +35,16 @@ impl fmt::Display for RadrootsCoreMoneyInvariantError { #[cfg(feature = "std")] impl std::error::Error for RadrootsCoreMoneyInvariantError {} -impl RadrootsCoreMoney { +impl Money { #[inline] - pub fn new(amount: crate::RadrootsCoreDecimal, currency: crate::RadrootsCoreCurrency) -> Self { + pub fn new(amount: crate::Decimal, currency: crate::Currency) -> Self { Self { amount, currency } } #[inline] - pub fn zero(currency: crate::RadrootsCoreCurrency) -> Self { + pub fn zero(currency: crate::Currency) -> Self { Self { - amount: crate::RadrootsCoreDecimal::ZERO, + amount: crate::Decimal::ZERO, currency, } } @@ -97,24 +97,24 @@ impl RadrootsCoreMoney { } #[inline] - pub fn mul_decimal(&self, factor: crate::RadrootsCoreDecimal) -> Self { + pub fn mul_decimal(&self, factor: crate::Decimal) -> Self { Self::new(self.amount * factor, self.currency) } #[inline] - pub fn div_decimal(&self, divisor: crate::RadrootsCoreDecimal) -> Self { + pub fn div_decimal(&self, divisor: crate::Decimal) -> Self { Self::new(self.amount / divisor, self.currency) } #[inline] - pub fn from_minor_units_u64(amount_minor: u64, currency: crate::RadrootsCoreCurrency) -> Self { + pub fn from_minor_units_u64(amount_minor: u64, currency: crate::Currency) -> Self { let e = currency.minor_unit_exponent(); let major = Decimal::from_i128_with_scale(amount_minor as i128, e); - Self::new(crate::RadrootsCoreDecimal(major), currency) + Self::new(crate::Decimal(major), currency) } #[inline] - pub fn from_minor_units_u32(amount_minor: u32, currency: crate::RadrootsCoreCurrency) -> Self { + pub fn from_minor_units_u32(amount_minor: u32, currency: crate::Currency) -> Self { Self::from_minor_units_u64(amount_minor as u64, currency) } @@ -175,7 +175,7 @@ impl RadrootsCoreMoney { } } -impl fmt::Display for RadrootsCoreMoney { +impl fmt::Display for Money { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", self.amount, self.currency) } @@ -183,30 +183,33 @@ impl fmt::Display for RadrootsCoreMoney { use core::ops::{Div, Mul}; -impl Mul<crate::RadrootsCoreDecimal> for RadrootsCoreMoney { +impl Mul<crate::Decimal> for Money { type Output = Self; - fn mul(self, rhs: crate::RadrootsCoreDecimal) -> Self { + fn mul(self, rhs: crate::Decimal) -> Self { self.mul_decimal(rhs) } } -impl Div<crate::RadrootsCoreDecimal> for RadrootsCoreMoney { +impl Div<crate::Decimal> for Money { type Output = Self; - fn div(self, rhs: crate::RadrootsCoreDecimal) -> Self { + fn div(self, rhs: crate::Decimal) -> Self { self.div_decimal(rhs) } } +#[deprecated(since = "0.1.0", note = "renamed to `Money`")] +pub use self::Money as RadrootsCoreMoney; + #[cfg(test)] mod tests { use super::*; #[test] fn pow10_internal_paths_cover_fallback_branches() { - assert_eq!(RadrootsCoreMoney::pow10(0), Decimal::ONE); - assert_eq!(RadrootsCoreMoney::pow10(1), Decimal::from(10u32)); - assert_eq!(RadrootsCoreMoney::pow10(2), Decimal::from(100u32)); - assert_eq!(RadrootsCoreMoney::pow10(3), Decimal::from(1_000u32)); - assert_eq!(RadrootsCoreMoney::pow10(6), Decimal::from(1_000_000u32)); + assert_eq!(Money::pow10(0), Decimal::ONE); + assert_eq!(Money::pow10(1), Decimal::from(10u32)); + assert_eq!(Money::pow10(2), Decimal::from(100u32)); + assert_eq!(Money::pow10(3), Decimal::from(1_000u32)); + assert_eq!(Money::pow10(6), Decimal::from(1_000_000u32)); } } diff --git a/crates/core/src/percent.rs b/crates/core/src/percent.rs @@ -1,64 +1,64 @@ use core::fmt; use core::str::FromStr; -use crate::RadrootsCoreDecimal; -use crate::money::RadrootsCoreMoney; +use crate::Decimal; +use crate::money::Money; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsCorePercent { +pub struct Percent { #[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string"))] - pub value: RadrootsCoreDecimal, + pub value: Decimal, } -impl RadrootsCorePercent { +impl Percent { #[inline] - pub fn new(value: RadrootsCoreDecimal) -> Self { + pub fn new(value: Decimal) -> Self { Self { value } } #[inline] - pub fn from_ratio(ratio_0_to_1: RadrootsCoreDecimal) -> Self { + pub fn from_ratio(ratio_0_to_1: Decimal) -> Self { Self { - value: ratio_0_to_1 * RadrootsCoreDecimal::from(100u32), + value: ratio_0_to_1 * Decimal::from(100u32), } } #[inline] - pub fn to_ratio(&self) -> RadrootsCoreDecimal { - self.value / RadrootsCoreDecimal::from(100u32) + pub fn to_ratio(&self) -> Decimal { + self.value / Decimal::from(100u32) } #[inline] - pub fn of_money(&self, base: &RadrootsCoreMoney) -> RadrootsCoreMoney { + pub fn of_money(&self, base: &Money) -> Money { base.mul_decimal(self.to_ratio()) } #[inline] - pub fn of_money_quantized(&self, base: &RadrootsCoreMoney) -> RadrootsCoreMoney { + pub fn of_money_quantized(&self, base: &Money) -> Money { base.mul_decimal(self.to_ratio()).quantize_to_currency() } } -impl fmt::Display for RadrootsCorePercent { +impl fmt::Display for Percent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}%", self.value.normalize()) } } -impl FromStr for RadrootsCorePercent { +impl FromStr for Percent { type Err = RadrootsCorePercentParseError; fn from_str(s: &str) -> Result<Self, Self::Err> { let trimmed = s.trim_end(); let no_pct = trimmed.strip_suffix('%').unwrap_or(trimmed).trim(); let dec = no_pct - .parse::<RadrootsCoreDecimal>() + .parse::<Decimal>() .map_err(|_| RadrootsCorePercentParseError::InvalidNumber)?; - Ok(RadrootsCorePercent::new(dec)) + Ok(Percent::new(dec)) } } @@ -78,3 +78,6 @@ impl fmt::Display for RadrootsCorePercentParseError { #[cfg(feature = "std")] impl std::error::Error for RadrootsCorePercentParseError {} + +#[deprecated(since = "0.1.0", note = "renamed to `Percent`")] +pub use self::Percent as RadrootsCorePercent; diff --git a/crates/core/src/quantity.rs b/crates/core/src/quantity.rs @@ -1,7 +1,7 @@ use core::fmt; -use crate::RadrootsCoreDecimal; -use crate::unit::{RadrootsCoreUnit, RadrootsCoreUnitConvertError, convert_unit_decimal}; +use crate::Decimal; +use crate::unit::{RadrootsCoreUnitConvertError, Unit, convert_unit_decimal}; #[cfg(not(feature = "std"))] use alloc::string::String; @@ -12,18 +12,18 @@ use std::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsCoreQuantity { +pub struct Quantity { #[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string"))] - pub amount: RadrootsCoreDecimal, - pub unit: RadrootsCoreUnit, + pub amount: Decimal, + pub unit: Unit, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub label: Option<String>, } -impl RadrootsCoreQuantity { +impl Quantity { #[inline] - pub fn new(amount: RadrootsCoreDecimal, unit: RadrootsCoreUnit) -> Self { + pub fn new(amount: Decimal, unit: Unit) -> Self { Self { amount, unit, @@ -50,9 +50,9 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn zero(unit: RadrootsCoreUnit) -> Self { + pub fn zero(unit: Unit) -> Self { Self { - amount: RadrootsCoreDecimal::ZERO, + amount: Decimal::ZERO, unit, label: None, } @@ -69,20 +69,17 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn canonical_unit(&self) -> RadrootsCoreUnit { + pub fn canonical_unit(&self) -> Unit { self.unit.canonical_unit() } #[inline] - pub fn try_convert_to( - &self, - unit: RadrootsCoreUnit, - ) -> Result<RadrootsCoreQuantity, RadrootsCoreUnitConvertError> { + pub fn try_convert_to(&self, unit: Unit) -> Result<Quantity, RadrootsCoreUnitConvertError> { if self.unit == unit { return Ok(self.clone()); } let amount = convert_unit_decimal(self.amount, self.unit, unit)?; - Ok(RadrootsCoreQuantity { + Ok(Quantity { amount, unit, label: self.label.clone(), @@ -90,7 +87,7 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn to_canonical(&self) -> Result<RadrootsCoreQuantity, RadrootsCoreUnitConvertError> { + pub fn to_canonical(&self) -> Result<Quantity, RadrootsCoreUnitConvertError> { self.try_convert_to(self.unit.canonical_unit()) } @@ -109,14 +106,11 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn try_add( - &self, - rhs: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> { + pub fn try_add(&self, rhs: &Quantity) -> Result<Quantity, RadrootsCoreQuantityInvariantError> { if self.unit != rhs.unit { return Err(RadrootsCoreQuantityInvariantError::UnitMismatch); } - Ok(RadrootsCoreQuantity { + Ok(Quantity { amount: self.amount + rhs.amount, unit: self.unit, label: self.label.clone(), @@ -124,23 +118,20 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn try_sub( - &self, - rhs: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> { + pub fn try_sub(&self, rhs: &Quantity) -> Result<Quantity, RadrootsCoreQuantityInvariantError> { if self.unit != rhs.unit { return Err(RadrootsCoreQuantityInvariantError::UnitMismatch); } - Ok(RadrootsCoreQuantity { + Ok(Quantity { amount: self.amount - rhs.amount, unit: self.unit, label: self.label.clone(), }) } - pub fn checked_add(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> { + pub fn checked_add(&self, rhs: &Quantity) -> Option<Quantity> { if self.unit == rhs.unit { - Some(RadrootsCoreQuantity { + Some(Quantity { amount: self.amount + rhs.amount, unit: self.unit, label: self.label.clone(), @@ -150,9 +141,9 @@ impl RadrootsCoreQuantity { } } - pub fn checked_sub(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> { + pub fn checked_sub(&self, rhs: &Quantity) -> Option<Quantity> { if self.unit == rhs.unit { - Some(RadrootsCoreQuantity { + Some(Quantity { amount: self.amount - rhs.amount, unit: self.unit, label: self.label.clone(), @@ -163,8 +154,8 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn mul_decimal(&self, factor: RadrootsCoreDecimal) -> RadrootsCoreQuantity { - RadrootsCoreQuantity { + pub fn mul_decimal(&self, factor: Decimal) -> Quantity { + Quantity { amount: self.amount * factor, unit: self.unit, label: self.label.clone(), @@ -172,8 +163,8 @@ impl RadrootsCoreQuantity { } #[inline] - pub fn div_decimal(&self, divisor: RadrootsCoreDecimal) -> RadrootsCoreQuantity { - RadrootsCoreQuantity { + pub fn div_decimal(&self, divisor: Decimal) -> Quantity { + Quantity { amount: self.amount / divisor, unit: self.unit, label: self.label.clone(), @@ -181,7 +172,7 @@ impl RadrootsCoreQuantity { } } -impl fmt::Display for RadrootsCoreQuantity { +impl fmt::Display for Quantity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", self.amount.normalize(), self.unit)?; if let Some(label) = &self.label { @@ -216,10 +207,10 @@ impl std::error::Error for RadrootsCoreQuantityInvariantError {} use core::ops::{Div, Mul}; -impl Mul<RadrootsCoreDecimal> for RadrootsCoreQuantity { - type Output = RadrootsCoreQuantity; - fn mul(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity { - RadrootsCoreQuantity { +impl Mul<Decimal> for Quantity { + type Output = Quantity; + fn mul(self, rhs: Decimal) -> Quantity { + Quantity { amount: self.amount * rhs, unit: self.unit, label: self.label, @@ -227,13 +218,16 @@ impl Mul<RadrootsCoreDecimal> for RadrootsCoreQuantity { } } -impl Div<RadrootsCoreDecimal> for RadrootsCoreQuantity { - type Output = RadrootsCoreQuantity; - fn div(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity { - RadrootsCoreQuantity { +impl Div<Decimal> for Quantity { + type Output = Quantity; + fn div(self, rhs: Decimal) -> Quantity { + Quantity { amount: self.amount / rhs, unit: self.unit, label: self.label, } } } + +#[deprecated(since = "0.1.0", note = "renamed to `Quantity`")] +pub use self::Quantity as RadrootsCoreQuantity; diff --git a/crates/core/src/quantity_price.rs b/crates/core/src/quantity_price.rs @@ -1,60 +1,49 @@ -use crate::{RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreUnit}; +use crate::{Decimal, Money, Quantity, Unit}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsCoreQuantityPrice { - pub amount: RadrootsCoreMoney, - pub quantity: RadrootsCoreQuantity, +pub struct QuantityPrice { + pub amount: Money, + pub quantity: Quantity, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RadrootsCoreQuantityPriceError { PerQuantityZero, - UnitMismatch { - have: RadrootsCoreUnit, - want: RadrootsCoreUnit, - }, - NonConvertibleUnits { - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, - }, + UnitMismatch { have: Unit, want: Unit }, + NonConvertibleUnits { from: Unit, to: Unit }, } pub trait RadrootsCoreQuantityPriceOps { #[must_use] - fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + fn cost_for(&self, qty: &Quantity) -> Money; #[must_use] - fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + fn cost_for_rounded(&self, qty: &Quantity) -> Money; #[must_use] - fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + fn cost_for_with_quantized_price(&self, qty: &Quantity) -> Money; - fn try_cost_for( - &self, - qty: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; + fn try_cost_for(&self, qty: &Quantity) -> Result<Money, RadrootsCoreQuantityPriceError>; - fn try_cost_for_rounded( - &self, - qty: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; + fn try_cost_for_rounded(&self, qty: &Quantity) + -> Result<Money, RadrootsCoreQuantityPriceError>; } -impl RadrootsCoreQuantityPrice { +impl QuantityPrice { #[inline] - pub fn new(amount: RadrootsCoreMoney, quantity: RadrootsCoreQuantity) -> Self { + pub fn new(amount: Money, quantity: Quantity) -> Self { Self { amount, quantity } } #[inline] pub fn try_cost_for_amount_in( &self, - amount: RadrootsCoreDecimal, - unit: RadrootsCoreUnit, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + amount: Decimal, + unit: Unit, + ) -> Result<Money, RadrootsCoreQuantityPriceError> { use crate::unit::convert_unit_decimal; let target = self.quantity.unit; @@ -70,29 +59,29 @@ impl RadrootsCoreQuantityPrice { })? }; - let qty = RadrootsCoreQuantity::new(normalized, target); + let qty = Quantity::new(normalized, target); self.try_cost_for_rounded(&qty) } #[inline] pub fn try_cost_for_quantity_in( &self, - qty: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + qty: &Quantity, + ) -> Result<Money, RadrootsCoreQuantityPriceError> { self.try_cost_for_amount_in(qty.amount, qty.unit) } #[inline] pub fn is_price_per_canonical_unit(&self) -> bool { self.quantity.unit == self.quantity.unit.canonical_unit() - && self.quantity.amount == RadrootsCoreDecimal::ONE + && self.quantity.amount == Decimal::ONE } #[inline] pub fn try_to_unit_price( &self, - unit: RadrootsCoreUnit, - ) -> Result<RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError> { + unit: Unit, + ) -> Result<QuantityPrice, RadrootsCoreQuantityPriceError> { use crate::unit::convert_unit_decimal; if self.quantity.amount.is_zero() { @@ -115,31 +104,31 @@ impl RadrootsCoreQuantityPrice { } let amount = self.amount.div_decimal(normalized); - Ok(RadrootsCoreQuantityPrice { + Ok(QuantityPrice { amount, - quantity: RadrootsCoreQuantity::new(RadrootsCoreDecimal::ONE, unit), + quantity: Quantity::new(Decimal::ONE, unit), }) } #[inline] pub fn try_to_canonical_unit_price( &self, - ) -> Result<RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError> { + ) -> Result<QuantityPrice, RadrootsCoreQuantityPriceError> { self.try_to_unit_price(self.quantity.unit.canonical_unit()) } } -impl RadrootsCoreQuantityPriceOps for RadrootsCoreQuantityPrice { +impl RadrootsCoreQuantityPriceOps for QuantityPrice { #[inline] - fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + fn cost_for(&self, qty: &Quantity) -> Money { if qty.amount.is_zero() { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } if self.quantity.amount.is_zero() { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } if qty.unit != self.quantity.unit { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } let ratio = qty.amount / self.quantity.amount; @@ -147,30 +136,27 @@ impl RadrootsCoreQuantityPriceOps for RadrootsCoreQuantityPrice { } #[inline] - fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + fn cost_for_rounded(&self, qty: &Quantity) -> Money { self.cost_for(qty).quantize_to_currency() } #[inline] - fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + fn cost_for_with_quantized_price(&self, qty: &Quantity) -> Money { if qty.amount.is_zero() { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } if self.quantity.amount.is_zero() { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } if qty.unit != self.quantity.unit { - return RadrootsCoreMoney::zero(self.amount.currency); + return Money::zero(self.amount.currency); } let unit_price_q = self.amount.clone().quantize_to_currency(); unit_price_q.mul_decimal(qty.amount / self.quantity.amount) } #[inline] - fn try_cost_for( - &self, - qty: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + fn try_cost_for(&self, qty: &Quantity) -> Result<Money, RadrootsCoreQuantityPriceError> { if self.quantity.amount.is_zero() { return Err(RadrootsCoreQuantityPriceError::PerQuantityZero); } @@ -187,8 +173,11 @@ impl RadrootsCoreQuantityPriceOps for RadrootsCoreQuantityPrice { #[inline] fn try_cost_for_rounded( &self, - qty: &RadrootsCoreQuantity, - ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + qty: &Quantity, + ) -> Result<Money, RadrootsCoreQuantityPriceError> { Ok(self.try_cost_for(qty)?.quantize_to_currency()) } } + +#[deprecated(since = "0.1.0", note = "renamed to `QuantityPrice`")] +pub use self::QuantityPrice as RadrootsCoreQuantityPrice; diff --git a/crates/core/src/serde_ext.rs b/crates/core/src/serde_ext.rs @@ -7,20 +7,15 @@ use serde::{Deserialize, Deserializer, Serializer, de::Error as DeError}; pub mod decimal_str { use super::*; - use crate::RadrootsCoreDecimal; + use crate::Decimal; use core::str::FromStr; - pub fn serialize<S: Serializer>( - value: &RadrootsCoreDecimal, - serializer: S, - ) -> Result<S::Ok, S::Error> { + pub fn serialize<S: Serializer>(value: &Decimal, serializer: S) -> Result<S::Ok, S::Error> { serializer.serialize_str(&value.normalize().to_string()) } - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result<RadrootsCoreDecimal, D::Error> { + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Decimal, D::Error> { let s = String::deserialize(deserializer)?; - RadrootsCoreDecimal::from_str(&s).map_err(D::Error::custom) + Decimal::from_str(&s).map_err(D::Error::custom) } } diff --git a/crates/core/src/unit.rs b/crates/core/src/unit.rs @@ -11,13 +11,13 @@ use std::string::String; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; -use crate::RadrootsCoreDecimal; +use crate::Decimal; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RadrootsCoreUnitDimension { +pub enum UnitDimension { #[cfg_attr(feature = "dto-bindgen", dto(rename = "count"))] Count, #[cfg_attr(feature = "dto-bindgen", dto(rename = "mass"))] @@ -30,7 +30,7 @@ pub enum RadrootsCoreUnitDimension { #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(as = "string_enum"))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RadrootsCoreUnit { +pub enum Unit { #[cfg_attr(feature = "dto-bindgen", dto(rename = "each"))] Each, #[cfg_attr(feature = "dto-bindgen", dto(rename = "kg"))] @@ -47,7 +47,7 @@ pub enum RadrootsCoreUnit { VolumeMl, } -impl RadrootsCoreUnit { +impl Unit { #[inline] pub fn code(&self) -> &'static str { match self { @@ -66,22 +66,20 @@ impl RadrootsCoreUnit { } #[inline] - pub fn dimension(&self) -> RadrootsCoreUnitDimension { + pub fn dimension(&self) -> UnitDimension { match self { - Self::Each => RadrootsCoreUnitDimension::Count, - Self::MassKg | Self::MassG | Self::MassOz | Self::MassLb => { - RadrootsCoreUnitDimension::Mass - } - Self::VolumeL | Self::VolumeMl => RadrootsCoreUnitDimension::Volume, + Self::Each => UnitDimension::Count, + Self::MassKg | Self::MassG | Self::MassOz | Self::MassLb => UnitDimension::Mass, + Self::VolumeL | Self::VolumeMl => UnitDimension::Volume, } } #[inline] pub fn canonical_unit(&self) -> Self { match self.dimension() { - RadrootsCoreUnitDimension::Count => Self::Each, - RadrootsCoreUnitDimension::Mass => Self::MassG, - RadrootsCoreUnitDimension::Volume => Self::VolumeMl, + UnitDimension::Count => Self::Each, + UnitDimension::Mass => Self::MassG, + UnitDimension::Volume => Self::VolumeMl, } } @@ -104,7 +102,7 @@ impl RadrootsCoreUnit { } } -impl fmt::Display for RadrootsCoreUnit { +impl fmt::Display for Unit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.code()) } @@ -132,18 +130,9 @@ impl std::error::Error for RadrootsCoreUnitParseError {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RadrootsCoreUnitConvertError { - NotMassUnit { - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, - }, - NotVolumeUnit { - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, - }, - NotConvertibleUnits { - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, - }, + NotMassUnit { from: Unit, to: Unit }, + NotVolumeUnit { from: Unit, to: Unit }, + NotConvertibleUnits { from: Unit, to: Unit }, } impl fmt::Display for RadrootsCoreUnitConvertError { @@ -168,20 +157,20 @@ impl fmt::Display for RadrootsCoreUnitConvertError { #[cfg(feature = "std")] impl std::error::Error for RadrootsCoreUnitConvertError {} -impl FromStr for RadrootsCoreUnit { +impl FromStr for Unit { type Err = RadrootsCoreUnitParseError; fn from_str(s: &str) -> Result<Self, Self::Err> { let s = s.trim().to_ascii_lowercase(); match s.as_str() { - "each" | "ea" | "count" => Ok(RadrootsCoreUnit::Each), - "kg" | "kilogram" | "kilograms" => Ok(RadrootsCoreUnit::MassKg), - "g" | "gram" | "grams" => Ok(RadrootsCoreUnit::MassG), - "oz" | "ounce" | "ounces" => Ok(RadrootsCoreUnit::MassOz), - "lb" | "pound" | "pounds" => Ok(RadrootsCoreUnit::MassLb), - "l" | "liter" | "litre" | "liters" | "litres" => Ok(RadrootsCoreUnit::VolumeL), + "each" | "ea" | "count" => Ok(Unit::Each), + "kg" | "kilogram" | "kilograms" => Ok(Unit::MassKg), + "g" | "gram" | "grams" => Ok(Unit::MassG), + "oz" | "ounce" | "ounces" => Ok(Unit::MassOz), + "lb" | "pound" | "pounds" => Ok(Unit::MassLb), + "l" | "liter" | "litre" | "liters" | "litres" => Ok(Unit::VolumeL), "ml" | "milliliter" | "millilitre" | "milliliters" | "millilitres" => { - Ok(RadrootsCoreUnit::VolumeMl) + Ok(Unit::VolumeMl) } _ => Err(RadrootsCoreUnitParseError::UnknownUnit), } @@ -189,23 +178,29 @@ impl FromStr for RadrootsCoreUnit { } #[cfg(feature = "serde")] -impl Serialize for RadrootsCoreUnit { +impl Serialize for Unit { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_str(self.code()) } } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for RadrootsCoreUnit { +impl<'de> Deserialize<'de> for Unit { fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { let s = String::deserialize(de)?; s.parse().map_err(D::Error::custom) } } +#[deprecated(since = "0.1.0", note = "renamed to `UnitDimension`")] +pub use self::UnitDimension as RadrootsCoreUnitDimension; + +#[deprecated(since = "0.1.0", note = "renamed to `Unit`")] +pub use self::Unit as RadrootsCoreUnit; + #[inline] -pub fn parse_mass_unit(s: &str) -> Result<RadrootsCoreUnit, RadrootsCoreUnitParseError> { - let u: RadrootsCoreUnit = RadrootsCoreUnit::from_str(s)?; +pub fn parse_mass_unit(s: &str) -> Result<Unit, RadrootsCoreUnitParseError> { + let u: Unit = Unit::from_str(s)?; if u.is_mass() { Ok(u) } else { @@ -214,8 +209,8 @@ pub fn parse_mass_unit(s: &str) -> Result<RadrootsCoreUnit, RadrootsCoreUnitPars } #[inline] -pub fn parse_volume_unit(s: &str) -> Result<RadrootsCoreUnit, RadrootsCoreUnitParseError> { - let u: RadrootsCoreUnit = RadrootsCoreUnit::from_str(s)?; +pub fn parse_volume_unit(s: &str) -> Result<Unit, RadrootsCoreUnitParseError> { + let u: Unit = Unit::from_str(s)?; if u.is_volume() { Ok(u) } else { @@ -225,25 +220,25 @@ pub fn parse_volume_unit(s: &str) -> Result<RadrootsCoreUnit, RadrootsCoreUnitPa #[inline] pub fn convert_mass_decimal( - amount: RadrootsCoreDecimal, - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, -) -> Result<RadrootsCoreDecimal, RadrootsCoreUnitConvertError> { + amount: Decimal, + from: Unit, + to: Unit, +) -> Result<Decimal, RadrootsCoreUnitConvertError> { let amount_g = match from { - RadrootsCoreUnit::MassG => amount, - RadrootsCoreUnit::MassKg => amount * RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassOz => amount * RadrootsCoreDecimal(dec!(28.349523125)), - RadrootsCoreUnit::MassLb => amount * RadrootsCoreDecimal(dec!(453.59237)), + Unit::MassG => amount, + Unit::MassKg => amount * Decimal::from(1000u32), + Unit::MassOz => amount * Decimal(dec!(28.349523125)), + Unit::MassLb => amount * Decimal(dec!(453.59237)), _ => { return Err(RadrootsCoreUnitConvertError::NotMassUnit { from, to }); } }; let to_factor = match to { - RadrootsCoreUnit::MassG => RadrootsCoreDecimal::ONE, - RadrootsCoreUnit::MassKg => RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassOz => RadrootsCoreDecimal(dec!(28.349523125)), - RadrootsCoreUnit::MassLb => RadrootsCoreDecimal(dec!(453.59237)), + Unit::MassG => Decimal::ONE, + Unit::MassKg => Decimal::from(1000u32), + Unit::MassOz => Decimal(dec!(28.349523125)), + Unit::MassLb => Decimal(dec!(453.59237)), _ => { return Err(RadrootsCoreUnitConvertError::NotMassUnit { from, to }); } @@ -254,21 +249,21 @@ pub fn convert_mass_decimal( #[inline] pub fn convert_volume_decimal( - amount: RadrootsCoreDecimal, - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, -) -> Result<RadrootsCoreDecimal, RadrootsCoreUnitConvertError> { + amount: Decimal, + from: Unit, + to: Unit, +) -> Result<Decimal, RadrootsCoreUnitConvertError> { let amount_ml = match from { - RadrootsCoreUnit::VolumeMl => amount, - RadrootsCoreUnit::VolumeL => amount * RadrootsCoreDecimal::from(1000u32), + Unit::VolumeMl => amount, + Unit::VolumeL => amount * Decimal::from(1000u32), _ => { return Err(RadrootsCoreUnitConvertError::NotVolumeUnit { from, to }); } }; let to_factor = match to { - RadrootsCoreUnit::VolumeMl => RadrootsCoreDecimal::ONE, - RadrootsCoreUnit::VolumeL => RadrootsCoreDecimal::from(1000u32), + Unit::VolumeMl => Decimal::ONE, + Unit::VolumeL => Decimal::from(1000u32), _ => { return Err(RadrootsCoreUnitConvertError::NotVolumeUnit { from, to }); } @@ -279,17 +274,17 @@ pub fn convert_volume_decimal( #[inline] pub fn convert_unit_decimal( - amount: RadrootsCoreDecimal, - from: RadrootsCoreUnit, - to: RadrootsCoreUnit, -) -> Result<RadrootsCoreDecimal, RadrootsCoreUnitConvertError> { - if !RadrootsCoreUnit::same_dimension(from, to) { + amount: Decimal, + from: Unit, + to: Unit, +) -> Result<Decimal, RadrootsCoreUnitConvertError> { + if !Unit::same_dimension(from, to) { return Err(RadrootsCoreUnitConvertError::NotConvertibleUnits { from, to }); } match from.dimension() { - RadrootsCoreUnitDimension::Count => Ok(amount), - RadrootsCoreUnitDimension::Mass => convert_mass_decimal(amount, from, to), - RadrootsCoreUnitDimension::Volume => convert_volume_decimal(amount, from, to), + UnitDimension::Count => Ok(amount), + UnitDimension::Mass => convert_mass_decimal(amount, from, to), + UnitDimension::Volume => convert_volume_decimal(amount, from, to), } } @@ -300,25 +295,17 @@ mod tests { #[test] fn convert_paths_cover_unit_branches() { assert_eq!( - convert_mass_decimal( - RadrootsCoreDecimal::ONE, - RadrootsCoreUnit::Each, - RadrootsCoreUnit::MassG - ), + convert_mass_decimal(Decimal::ONE, Unit::Each, Unit::MassG), Err(RadrootsCoreUnitConvertError::NotMassUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG }) ); assert_eq!( - convert_volume_decimal( - RadrootsCoreDecimal::ONE, - RadrootsCoreUnit::Each, - RadrootsCoreUnit::VolumeMl - ), + convert_volume_decimal(Decimal::ONE, Unit::Each, Unit::VolumeMl), Err(RadrootsCoreUnitConvertError::NotVolumeUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::VolumeMl + from: Unit::Each, + to: Unit::VolumeMl }) ); } diff --git a/crates/core/tests/common/mod.rs b/crates/core/tests/common/mod.rs @@ -2,27 +2,24 @@ use core::str::FromStr; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCorePercent, - RadrootsCoreQuantity, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Percent, Quantity, Unit}; -pub fn dec(s: &str) -> RadrootsCoreDecimal { - RadrootsCoreDecimal::from_str(s).expect("valid decimal") +pub fn dec(s: &str) -> Decimal { + Decimal::from_str(s).expect("valid decimal") } -pub fn currency(code: &str) -> RadrootsCoreCurrency { - RadrootsCoreCurrency::from_str(code).expect("valid currency") +pub fn currency(code: &str) -> Currency { + Currency::from_str(code).expect("valid currency") } -pub fn money(amount: &str, code: &str) -> RadrootsCoreMoney { - RadrootsCoreMoney::new(dec(amount), currency(code)) +pub fn money(amount: &str, code: &str) -> Money { + Money::new(dec(amount), currency(code)) } -pub fn qty(amount: &str, unit: RadrootsCoreUnit) -> RadrootsCoreQuantity { - RadrootsCoreQuantity::new(dec(amount), unit) +pub fn qty(amount: &str, unit: Unit) -> Quantity { + Quantity::new(dec(amount), unit) } -pub fn percent(s: &str) -> RadrootsCorePercent { - RadrootsCorePercent::from_str(s).expect("valid percent") +pub fn percent(s: &str) -> Percent { + Percent::from_str(s).expect("valid percent") } diff --git a/crates/core/tests/currency.rs b/crates/core/tests/currency.rs @@ -1,69 +1,69 @@ use core::str::FromStr; -use radroots_core::{RadrootsCoreCurrency, RadrootsCoreCurrencyParseError}; +use radroots_core::{Currency, RadrootsCoreCurrencyParseError}; #[test] fn from_str_upper_accepts_valid() { - let usd = RadrootsCoreCurrency::from_str_upper("USD").unwrap(); + let usd = Currency::from_str_upper("USD").unwrap(); assert_eq!(usd.as_str(), "USD"); } #[test] fn from_str_upper_rejects_invalid() { assert_eq!( - RadrootsCoreCurrency::from_str_upper("Usd"), + Currency::from_str_upper("Usd"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); assert_eq!( - RadrootsCoreCurrency::from_str_upper("US"), + Currency::from_str_upper("US"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); } #[test] fn from_str_trims_and_uppercases() { - let usd = RadrootsCoreCurrency::from_str(" usd ").unwrap(); + let usd = Currency::from_str(" usd ").unwrap(); assert_eq!(usd.as_str(), "USD"); } #[test] fn from_str_rejects_non_alpha() { assert_eq!( - RadrootsCoreCurrency::from_str("US1"), + Currency::from_str("US1"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); assert_eq!( - RadrootsCoreCurrency::from_str("US"), + Currency::from_str("US"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); } #[test] fn from_const_validates_bytes() { - assert!(RadrootsCoreCurrency::from_const(*b"USD").is_ok()); + assert!(Currency::from_const(*b"USD").is_ok()); assert_eq!( - RadrootsCoreCurrency::from_const(*b"1SD"), + Currency::from_const(*b"1SD"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); assert_eq!( - RadrootsCoreCurrency::from_const(*b"Usd"), + Currency::from_const(*b"Usd"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); assert_eq!( - RadrootsCoreCurrency::from_const(*b"USd"), + Currency::from_const(*b"USd"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); assert_eq!( - RadrootsCoreCurrency::from_const(*b"usd"), + Currency::from_const(*b"usd"), Err(RadrootsCoreCurrencyParseError::InvalidFormat) ); } #[test] fn minor_unit_exponent_matches_known_currencies() { - let jpy = RadrootsCoreCurrency::from_str_upper("JPY").unwrap(); - let kwd = RadrootsCoreCurrency::from_str_upper("KWD").unwrap(); - let usd = RadrootsCoreCurrency::from_str_upper("USD").unwrap(); + let jpy = Currency::from_str_upper("JPY").unwrap(); + let kwd = Currency::from_str_upper("KWD").unwrap(); + let usd = Currency::from_str_upper("USD").unwrap(); assert_eq!(jpy.minor_unit_exponent(), 0); assert_eq!(kwd.minor_unit_exponent(), 3); @@ -72,10 +72,10 @@ fn minor_unit_exponent_matches_known_currencies() { #[test] fn display_debug_tryfrom_and_error_display_paths_are_exercised() { - let usd = RadrootsCoreCurrency::from_str("usd").unwrap(); + let usd = Currency::from_str("usd").unwrap(); assert_eq!(usd.to_string(), "USD"); - assert_eq!(format!("{usd:?}"), "RadrootsCoreCurrency(\"USD\")"); - let via_try_from = RadrootsCoreCurrency::try_from("usd").unwrap(); + assert_eq!(format!("{usd:?}"), "Currency(\"USD\")"); + let via_try_from = Currency::try_from("usd").unwrap(); assert_eq!(via_try_from, usd); assert_eq!( RadrootsCoreCurrencyParseError::InvalidFormat.to_string(), @@ -86,10 +86,10 @@ fn display_debug_tryfrom_and_error_display_paths_are_exercised() { #[cfg(feature = "serde")] #[test] fn serde_deserialize_paths_are_exercised() { - let parsed: RadrootsCoreCurrency = serde_json::from_str("\"USD\"").unwrap(); + let parsed: Currency = serde_json::from_str("\"USD\"").unwrap(); assert_eq!(parsed.as_str(), "USD"); - let err = serde_json::from_str::<RadrootsCoreCurrency>("\"US1\"").unwrap_err(); + let err = serde_json::from_str::<Currency>("\"US1\"").unwrap_err(); assert!(err.to_string().contains("currency must be a 3-letter code")); - let non_string_err = serde_json::from_str::<RadrootsCoreCurrency>("123").unwrap_err(); + let non_string_err = serde_json::from_str::<Currency>("123").unwrap_err(); assert!(non_string_err.to_string().contains("invalid type")); } diff --git a/crates/core/tests/decimal.rs b/crates/core/tests/decimal.rs @@ -2,17 +2,17 @@ mod common; use core::str::FromStr; -use radroots_core::RadrootsCoreDecimal; +use radroots_core::Decimal; #[test] fn display_normalizes_trailing_zeros() { - let d = RadrootsCoreDecimal::from_str("1.2300").unwrap(); + let d = Decimal::from_str("1.2300").unwrap(); assert_eq!(d.to_string(), "1.23"); } #[test] fn scale_reflects_input_precision() { - let d = RadrootsCoreDecimal::from_str("1.2300").unwrap(); + let d = Decimal::from_str("1.2300").unwrap(); assert_eq!(d.scale(), 4); } @@ -26,25 +26,25 @@ fn to_u64_exact_requires_whole_number() { #[test] fn from_f64_display_roundtrips_reasonably() { - let d = RadrootsCoreDecimal::from_f64_display(1.25).unwrap(); + let d = Decimal::from_f64_display(1.25).unwrap(); let v = d.to_f64_lossy().expect("f64 conversion"); assert!((v - 1.25).abs() < 1e-12); } #[test] fn from_str_exact_and_conversion_impl_paths_are_exercised() { - let exact = RadrootsCoreDecimal::from_str_exact("42.000").unwrap(); + let exact = Decimal::from_str_exact("42.000").unwrap(); assert_eq!(exact, common::dec("42")); - let from_decimal = RadrootsCoreDecimal::from(rust_decimal::Decimal::from(5u32)); + let from_decimal = Decimal::from(rust_decimal::Decimal::from(5u32)); assert_eq!(from_decimal, common::dec("5")); let back: rust_decimal::Decimal = from_decimal.into(); assert_eq!(back, rust_decimal::Decimal::from(5u32)); - let from_u32 = RadrootsCoreDecimal::from(7u32); - let from_i32 = RadrootsCoreDecimal::from(-2i32); - let from_u64 = RadrootsCoreDecimal::from(11u64); - let from_i64 = RadrootsCoreDecimal::from(-9i64); + let from_u32 = Decimal::from(7u32); + let from_i32 = Decimal::from(-2i32); + let from_u64 = Decimal::from(11u64); + let from_i64 = Decimal::from(-9i64); assert_eq!(from_u32, common::dec("7")); assert_eq!(from_i32, common::dec("-2")); assert_eq!(from_u64, common::dec("11")); @@ -54,8 +54,8 @@ fn from_str_exact_and_conversion_impl_paths_are_exercised() { #[cfg(feature = "serde")] #[test] fn serde_deserialize_error_paths_are_exercised() { - let parse_err = serde_json::from_str::<RadrootsCoreDecimal>("\"not-a-decimal\"").unwrap_err(); + let parse_err = serde_json::from_str::<Decimal>("\"not-a-decimal\"").unwrap_err(); assert!(!parse_err.to_string().is_empty()); - let non_string_err = serde_json::from_str::<RadrootsCoreDecimal>("123").unwrap_err(); + let non_string_err = serde_json::from_str::<Decimal>("123").unwrap_err(); assert!(non_string_err.to_string().contains("invalid type")); } diff --git a/crates/core/tests/discount.rs b/crates/core/tests/discount.rs @@ -1,18 +1,18 @@ mod common; use radroots_core::{ - RadrootsCoreDiscount, RadrootsCoreDiscountScope, RadrootsCoreDiscountThreshold, - RadrootsCoreDiscountValue, RadrootsCorePercent, RadrootsCoreUnit, + Percent, RadrootsCoreDiscount, RadrootsCoreDiscountScope, RadrootsCoreDiscountThreshold, + RadrootsCoreDiscountValue, Unit, }; #[test] fn is_non_negative_validates_discount_shapes() { let money_pos = common::money("1", "USD"); let money_neg = common::money("-1", "USD"); - let qty_pos = common::qty("1", RadrootsCoreUnit::Each); - let qty_neg = common::qty("-1", RadrootsCoreUnit::Each); - let pct_pos = RadrootsCorePercent::new(common::dec("10")); - let pct_neg = RadrootsCorePercent::new(common::dec("-5")); + let qty_pos = common::qty("1", Unit::Each); + let qty_neg = common::qty("-1", Unit::Each); + let pct_pos = Percent::new(common::dec("10")); + let pct_neg = Percent::new(common::dec("-5")); let d = RadrootsCoreDiscount { scope: RadrootsCoreDiscountScope::Bin, diff --git a/crates/core/tests/money.rs b/crates/core/tests/money.rs @@ -1,19 +1,19 @@ mod common; -use radroots_core::{RadrootsCoreCurrency, RadrootsCoreMoney, RadrootsCoreMoneyInvariantError}; +use radroots_core::{Currency, Money, RadrootsCoreMoneyInvariantError}; use rust_decimal::RoundingStrategy; #[test] fn zero_and_is_zero() { - let usd = RadrootsCoreCurrency::USD; - let zero = RadrootsCoreMoney::zero(usd); + let usd = Currency::USD; + let zero = Money::zero(usd); assert!(zero.is_zero()); assert_eq!(zero.currency, usd); } #[test] fn ensure_non_negative_rejects_negative_amount() { - let money = RadrootsCoreMoney::new(common::dec("-1"), RadrootsCoreCurrency::USD); + let money = Money::new(common::dec("-1"), Currency::USD); assert_eq!( money.ensure_non_negative(), Err(RadrootsCoreMoneyInvariantError::NegativeAmount) @@ -22,18 +22,18 @@ fn ensure_non_negative_rejects_negative_amount() { #[test] fn ensure_non_negative_accepts_zero_and_positive() { - let zero = RadrootsCoreMoney::new(common::dec("0"), RadrootsCoreCurrency::USD); - let pos = RadrootsCoreMoney::new(common::dec("1"), RadrootsCoreCurrency::USD); + let zero = Money::new(common::dec("0"), Currency::USD); + let pos = Money::new(common::dec("1"), Currency::USD); assert_eq!(zero.ensure_non_negative(), Ok(())); assert_eq!(pos.ensure_non_negative(), Ok(())); } #[test] fn quantize_to_currency_rounds_midpoint_away_from_zero() { - let usd = RadrootsCoreCurrency::USD; - let a = RadrootsCoreMoney::new(common::dec("1.234"), usd).quantize_to_currency(); - let b = RadrootsCoreMoney::new(common::dec("1.235"), usd).quantize_to_currency(); - let c = RadrootsCoreMoney::new(common::dec("-1.235"), usd).quantize_to_currency(); + let usd = Currency::USD; + let a = Money::new(common::dec("1.234"), usd).quantize_to_currency(); + let b = Money::new(common::dec("1.235"), usd).quantize_to_currency(); + let c = Money::new(common::dec("-1.235"), usd).quantize_to_currency(); assert_eq!(a.amount, common::dec("1.23")); assert_eq!(b.amount, common::dec("1.24")); @@ -42,10 +42,10 @@ fn quantize_to_currency_rounds_midpoint_away_from_zero() { #[test] fn quantize_to_currency_with_strategy_uses_strategy() { - let usd = RadrootsCoreCurrency::USD; - let a = RadrootsCoreMoney::new(common::dec("1.235"), usd) + let usd = Currency::USD; + let a = Money::new(common::dec("1.235"), usd) .quantize_to_currency_with_strategy(RoundingStrategy::MidpointTowardZero); - let b = RadrootsCoreMoney::new(common::dec("-1.235"), usd) + let b = Money::new(common::dec("-1.235"), usd) .quantize_to_currency_with_strategy(RoundingStrategy::MidpointTowardZero); assert_eq!(a.amount, common::dec("1.23")); assert_eq!(b.amount, common::dec("-1.23")); @@ -53,11 +53,11 @@ fn quantize_to_currency_with_strategy_uses_strategy() { #[test] fn checked_add_and_sub_require_currency_match() { - let usd = RadrootsCoreCurrency::USD; - let eur = RadrootsCoreCurrency::EUR; - let a = RadrootsCoreMoney::new(common::dec("1.00"), usd); - let b = RadrootsCoreMoney::new(common::dec("2.00"), usd); - let c = RadrootsCoreMoney::new(common::dec("3.00"), eur); + let usd = Currency::USD; + let eur = Currency::EUR; + let a = Money::new(common::dec("1.00"), usd); + let b = Money::new(common::dec("2.00"), usd); + let c = Money::new(common::dec("3.00"), eur); assert_eq!(a.checked_add(&b).unwrap().amount, common::dec("3.00")); assert_eq!( @@ -69,10 +69,10 @@ fn checked_add_and_sub_require_currency_match() { #[test] fn checked_sub_mismatch_returns_currency_error() { - let usd = RadrootsCoreCurrency::USD; - let eur = RadrootsCoreCurrency::EUR; - let a = RadrootsCoreMoney::new(common::dec("1.00"), usd); - let b = RadrootsCoreMoney::new(common::dec("2.00"), eur); + let usd = Currency::USD; + let eur = Currency::EUR; + let a = Money::new(common::dec("1.00"), usd); + let b = Money::new(common::dec("2.00"), eur); assert_eq!( a.checked_sub(&b), Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch) @@ -81,10 +81,10 @@ fn checked_sub_mismatch_returns_currency_error() { #[test] fn minor_units_exact_and_rounded() { - let usd = RadrootsCoreCurrency::USD; - let exact = RadrootsCoreMoney::new(common::dec("1.23"), usd); - let frac = RadrootsCoreMoney::new(common::dec("1.234"), usd); - let rounded = RadrootsCoreMoney::new(common::dec("1.235"), usd); + let usd = Currency::USD; + let exact = Money::new(common::dec("1.23"), usd); + let frac = Money::new(common::dec("1.234"), usd); + let rounded = Money::new(common::dec("1.235"), usd); assert_eq!(exact.to_minor_units_u64_exact().unwrap(), 123); assert_eq!( @@ -101,7 +101,7 @@ fn minor_units_exact_and_rounded() { #[test] fn minor_units_cover_additional_currency_exponents() { - let jpy = RadrootsCoreMoney::new(common::dec("123"), common::currency("JPY")); + let jpy = Money::new(common::dec("123"), common::currency("JPY")); assert_eq!(jpy.to_minor_units_u64_exact().unwrap(), 123); assert_eq!( jpy.to_minor_units_u64_rounded(RoundingStrategy::MidpointAwayFromZero) @@ -109,14 +109,14 @@ fn minor_units_cover_additional_currency_exponents() { 123 ); - let kwd = RadrootsCoreMoney::new(common::dec("1.234"), common::currency("KWD")); + let kwd = Money::new(common::dec("1.234"), common::currency("KWD")); assert_eq!(kwd.to_minor_units_u64_exact().unwrap(), 1234); } #[test] fn minor_units_u32_overflow_is_detected() { - let usd = RadrootsCoreCurrency::USD; - let too_large = RadrootsCoreMoney::from_minor_units_u64(u64::from(u32::MAX) + 1, usd); + let usd = Currency::USD; + let too_large = Money::from_minor_units_u64(u64::from(u32::MAX) + 1, usd); assert_eq!( too_large.to_minor_units_u32_exact(), Err(RadrootsCoreMoneyInvariantError::AmountOverflow) @@ -125,10 +125,10 @@ fn minor_units_u32_overflow_is_detected() { #[test] fn minor_units_u32_exact_success_path_is_exercised() { - let usd = RadrootsCoreCurrency::USD; - let m = RadrootsCoreMoney::new(common::dec("42.01"), usd); + let usd = Currency::USD; + let m = Money::new(common::dec("42.01"), usd); assert_eq!(m.to_minor_units_u32_exact().unwrap(), 4201); - let fractional = RadrootsCoreMoney::new(common::dec("1.001"), usd); + let fractional = Money::new(common::dec("1.001"), usd); assert_eq!( fractional.to_minor_units_u32_exact(), Err(RadrootsCoreMoneyInvariantError::NotWholeMinorUnits) @@ -137,8 +137,8 @@ fn minor_units_u32_exact_success_path_is_exercised() { #[test] fn from_minor_units_u32_and_u32_rounded_paths_are_exercised() { - let usd = RadrootsCoreCurrency::USD; - let from_u32 = RadrootsCoreMoney::from_minor_units_u32(505, usd); + let usd = Currency::USD; + let from_u32 = Money::from_minor_units_u32(505, usd); assert_eq!(from_u32.amount, common::dec("5.05")); assert_eq!( from_u32 @@ -150,13 +150,13 @@ fn from_minor_units_u32_and_u32_rounded_paths_are_exercised() { #[test] fn minor_units_u32_rounded_overflow_is_detected() { - let usd = RadrootsCoreCurrency::USD; - let too_large = RadrootsCoreMoney::from_minor_units_u64(u64::from(u32::MAX) + 1, usd); + let usd = Currency::USD; + let too_large = Money::from_minor_units_u64(u64::from(u32::MAX) + 1, usd); assert_eq!( too_large.to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero), Err(RadrootsCoreMoneyInvariantError::AmountOverflow) ); - let negative = RadrootsCoreMoney::new(common::dec("-1.00"), usd); + let negative = Money::new(common::dec("-1.00"), usd); assert_eq!( negative.to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero), Err(RadrootsCoreMoneyInvariantError::AmountOverflow) @@ -165,22 +165,22 @@ fn minor_units_u32_rounded_overflow_is_detected() { #[test] fn with_scale_path_is_exercised() { - let usd = RadrootsCoreCurrency::USD; - let m = RadrootsCoreMoney::new(common::dec("1.2300"), usd).with_scale(1); + let usd = Currency::USD; + let m = Money::new(common::dec("1.2300"), usd).with_scale(1); assert_eq!(m.amount, common::dec("1.2")); } #[test] fn from_minor_units_roundtrips() { - let usd = RadrootsCoreCurrency::USD; - let money = RadrootsCoreMoney::from_minor_units_u64(12345, usd); + let usd = Currency::USD; + let money = Money::from_minor_units_u64(12345, usd); assert_eq!(money.to_minor_units_u64_exact().unwrap(), 12345); } #[test] fn display_and_operator_impl_paths_are_exercised() { - let usd = RadrootsCoreCurrency::USD; - let m = RadrootsCoreMoney::new(common::dec("10"), usd); + let usd = Currency::USD; + let m = Money::new(common::dec("10"), usd); assert_eq!(m.to_string(), "10 USD"); let times = m.clone() * common::dec("2"); diff --git a/crates/core/tests/package_boundary.rs b/crates/core/tests/package_boundary.rs @@ -1,7 +1,39 @@ +use radroots_core::unit::UnitDimension; #[allow(unused_imports)] use radroots_core::{ - currency as _, decimal as _, money as _, percent as _, pricing as _, quantity as _, unit as _, + Currency, Decimal, Money, Percent, Quantity, QuantityPrice, Unit, currency as _, decimal as _, + money as _, percent as _, pricing as _, quantity as _, unit as _, }; #[test] fn approved_module_skeleton_is_public() {} + +#[test] +fn canonical_value_paths_are_public() { + let _ = core::mem::size_of::<Currency>(); + let _ = core::mem::size_of::<Decimal>(); + let _ = core::mem::size_of::<Money>(); + let _ = core::mem::size_of::<Percent>(); + let _ = core::mem::size_of::<Quantity>(); + let _ = core::mem::size_of::<QuantityPrice>(); + let _ = core::mem::size_of::<Unit>(); + let _ = UnitDimension::Count; +} + +#[allow(deprecated)] +#[test] +fn temporary_legacy_aliases_remain_source_compatible() { + use radroots_core::{ + RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCorePercent, + RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, + RadrootsCoreUnitDimension, + }; + + let decimal = RadrootsCoreDecimal(rust_decimal::Decimal::ONE); + let currency = RadrootsCoreCurrency::USD; + let money = RadrootsCoreMoney::new(decimal, currency); + let quantity = RadrootsCoreQuantity::new(decimal, RadrootsCoreUnit::Each); + let _ = RadrootsCorePercent::new(decimal); + let _ = RadrootsCoreQuantityPrice::new(money, quantity); + let _ = RadrootsCoreUnitDimension::Count; +} diff --git a/crates/core/tests/percent.rs b/crates/core/tests/percent.rs @@ -2,25 +2,25 @@ mod common; use core::str::FromStr; -use radroots_core::{RadrootsCorePercent, RadrootsCorePercentParseError}; +use radroots_core::{Percent, RadrootsCorePercentParseError}; #[test] fn ratio_roundtrip() { - let pct = RadrootsCorePercent::from_ratio(common::dec("0.125")); + let pct = Percent::from_ratio(common::dec("0.125")); assert_eq!(pct.value, common::dec("12.5")); assert_eq!(pct.to_ratio(), common::dec("0.125")); } #[test] fn parses_percent_strings() { - let pct = RadrootsCorePercent::from_str("12.5%").unwrap(); + let pct = Percent::from_str("12.5%").unwrap(); assert_eq!(pct.value, common::dec("12.5")); - let pct = RadrootsCorePercent::from_str(" 12.5 ").unwrap(); + let pct = Percent::from_str(" 12.5 ").unwrap(); assert_eq!(pct.value, common::dec("12.5")); assert_eq!( - RadrootsCorePercent::from_str("nope"), + Percent::from_str("nope"), Err(RadrootsCorePercentParseError::InvalidNumber) ); } @@ -28,19 +28,19 @@ fn parses_percent_strings() { #[test] fn of_money_and_quantized() { let base = common::money("20.00", "USD"); - let pct = RadrootsCorePercent::from_str("10").unwrap(); + let pct = Percent::from_str("10").unwrap(); let out = pct.of_money(&base); assert_eq!(out.amount, common::dec("2.00")); let tiny = common::money("0.05", "USD"); - let pct = RadrootsCorePercent::from_str("10").unwrap(); + let pct = Percent::from_str("10").unwrap(); let rounded = pct.of_money_quantized(&tiny); assert_eq!(rounded.amount, common::dec("0.01")); } #[test] fn display_and_parse_error_display_paths_are_exercised() { - let pct = RadrootsCorePercent::from_str("12.5%").unwrap(); + let pct = Percent::from_str("12.5%").unwrap(); assert_eq!(pct.to_string(), "12.5%"); assert_eq!( RadrootsCorePercentParseError::InvalidNumber.to_string(), diff --git a/crates/core/tests/quantity.rs b/crates/core/tests/quantity.rs @@ -1,27 +1,27 @@ mod common; -use radroots_core::{RadrootsCoreQuantityInvariantError, RadrootsCoreUnit}; +use radroots_core::{RadrootsCoreQuantityInvariantError, Unit}; #[test] fn zero_helpers_and_scale_paths_are_exercised() { - let zero = radroots_core::RadrootsCoreQuantity::zero(RadrootsCoreUnit::MassKg); + let zero = radroots_core::Quantity::zero(Unit::MassKg); assert!(zero.is_zero()); - assert_eq!(zero.canonical_unit(), RadrootsCoreUnit::MassG); + assert_eq!(zero.canonical_unit(), Unit::MassG); assert!(!zero.is_canonical()); - let scaled = common::qty("1.2300", RadrootsCoreUnit::Each).with_scale(1); + let scaled = common::qty("1.2300", Unit::Each).with_scale(1); assert_eq!(scaled.amount, common::dec("1.2")); } #[test] fn label_helpers_set_and_clear() { - let q = common::qty("1", RadrootsCoreUnit::Each).with_label("box"); + let q = common::qty("1", Unit::Each).with_label("box"); assert_eq!(q.label.as_deref(), Some("box")); let q = q.clear_label(); assert!(q.label.is_none()); - let q = common::qty("1", RadrootsCoreUnit::Each).with_optional_label(Some("case")); + let q = common::qty("1", Unit::Each).with_optional_label(Some("case")); assert_eq!(q.label.as_deref(), Some("case")); let q = q.with_optional_label::<&str>(None); @@ -30,7 +30,7 @@ fn label_helpers_set_and_clear() { #[test] fn ensure_non_negative_rejects_negative_amount() { - let q = common::qty("-1", RadrootsCoreUnit::Each); + let q = common::qty("-1", Unit::Each); assert_eq!( q.ensure_non_negative(), Err(RadrootsCoreQuantityInvariantError::NegativeAmount) @@ -39,15 +39,15 @@ fn ensure_non_negative_rejects_negative_amount() { #[test] fn ensure_non_negative_accepts_non_negative_amount() { - let q = common::qty("0", RadrootsCoreUnit::Each); + let q = common::qty("0", Unit::Each); assert_eq!(q.ensure_non_negative(), Ok(())); } #[test] fn try_add_and_try_sub_require_matching_units() { - let a = common::qty("1", RadrootsCoreUnit::Each).with_label("lhs"); - let b = common::qty("2", RadrootsCoreUnit::Each); - let c = common::qty("1", RadrootsCoreUnit::MassKg); + let a = common::qty("1", Unit::Each).with_label("lhs"); + let b = common::qty("2", Unit::Each); + let c = common::qty("1", Unit::MassKg); let sum = a.try_add(&b).unwrap(); assert_eq!(sum.amount, common::dec("3")); @@ -65,8 +65,8 @@ fn try_add_and_try_sub_require_matching_units() { #[test] fn try_sub_success_path_is_exercised() { - let a = common::qty("4", RadrootsCoreUnit::Each).with_label("lhs"); - let b = common::qty("1", RadrootsCoreUnit::Each); + let a = common::qty("4", Unit::Each).with_label("lhs"); + let b = common::qty("1", Unit::Each); let out = a.try_sub(&b).expect("sub result"); assert_eq!(out.amount, common::dec("3")); assert_eq!(out.label.as_deref(), Some("lhs")); @@ -74,16 +74,16 @@ fn try_sub_success_path_is_exercised() { #[test] fn checked_add_and_sub_return_none_on_mismatch() { - let a = common::qty("1", RadrootsCoreUnit::Each); - let b = common::qty("2", RadrootsCoreUnit::MassG); + let a = common::qty("1", Unit::Each); + let b = common::qty("2", Unit::MassG); assert!(a.checked_add(&b).is_none()); assert!(a.checked_sub(&b).is_none()); } #[test] fn checked_add_and_sub_return_some_on_matching_units() { - let a = common::qty("5", RadrootsCoreUnit::Each).with_label("lhs"); - let b = common::qty("2", RadrootsCoreUnit::Each); + let a = common::qty("5", Unit::Each).with_label("lhs"); + let b = common::qty("2", Unit::Each); let added = a.checked_add(&b).expect("added quantity"); assert_eq!(added.amount, common::dec("7")); assert_eq!(added.label.as_deref(), Some("lhs")); @@ -95,21 +95,21 @@ fn checked_add_and_sub_return_some_on_matching_units() { #[test] fn mul_and_div_preserve_unit_and_label() { - let q = common::qty("2", RadrootsCoreUnit::Each).with_label("unit"); + let q = common::qty("2", Unit::Each).with_label("unit"); let scaled = q.clone().mul_decimal(common::dec("2.5")); assert_eq!(scaled.amount, common::dec("5")); - assert_eq!(scaled.unit, RadrootsCoreUnit::Each); + assert_eq!(scaled.unit, Unit::Each); assert_eq!(scaled.label.as_deref(), Some("unit")); let divided = q.div_decimal(common::dec("2")); assert_eq!(divided.amount, common::dec("1")); - assert_eq!(divided.unit, RadrootsCoreUnit::Each); + assert_eq!(divided.unit, Unit::Each); assert_eq!(divided.label.as_deref(), Some("unit")); } #[test] fn mul_and_div_operator_impls_are_exercised() { - let qty = common::qty("4", RadrootsCoreUnit::Each).with_label("bag"); + let qty = common::qty("4", Unit::Each).with_label("bag"); let mul = qty.clone() * common::dec("1.5"); assert_eq!(mul.amount, common::dec("6")); assert_eq!(mul.label.as_deref(), Some("bag")); @@ -121,13 +121,13 @@ fn mul_and_div_operator_impls_are_exercised() { #[test] fn display_includes_label_when_present() { - let q = common::qty("1.5", RadrootsCoreUnit::Each).with_label("bag"); + let q = common::qty("1.5", Unit::Each).with_label("bag"); assert_eq!(q.to_string(), "1.5 each (bag)"); } #[test] fn display_without_label_and_error_display_are_exercised() { - let q = common::qty("1.5", RadrootsCoreUnit::Each); + let q = common::qty("1.5", Unit::Each); assert_eq!(q.to_string(), "1.5 each"); assert_eq!( @@ -164,7 +164,7 @@ fn display_propagates_formatter_errors() { } } - let with_label = common::qty("1.5", RadrootsCoreUnit::Each).with_label("bag"); + let with_label = common::qty("1.5", Unit::Each).with_label("bag"); let mut first_write_fails = FailWriter { fail_on_paren: false, fail_on_call: 1, @@ -182,38 +182,36 @@ fn display_propagates_formatter_errors() { #[test] fn try_convert_to_changes_unit_and_amount() { - let q = common::qty("1", RadrootsCoreUnit::MassKg); - let converted = q.try_convert_to(RadrootsCoreUnit::MassG).unwrap(); + let q = common::qty("1", Unit::MassKg); + let converted = q.try_convert_to(Unit::MassG).unwrap(); assert_eq!(converted.amount, common::dec("1000")); - assert_eq!(converted.unit, RadrootsCoreUnit::MassG); + assert_eq!(converted.unit, Unit::MassG); } #[test] fn to_canonical_converts_mass_and_volume() { - let q = common::qty("2", RadrootsCoreUnit::VolumeL); + let q = common::qty("2", Unit::VolumeL); let canonical = q.to_canonical().unwrap(); - assert_eq!(canonical.unit, RadrootsCoreUnit::VolumeMl); + assert_eq!(canonical.unit, Unit::VolumeMl); assert_eq!(canonical.amount, common::dec("2000")); } #[test] fn try_convert_to_rejects_mismatched_dimensions() { - let q = common::qty("1", RadrootsCoreUnit::Each); - let err = q.try_convert_to(RadrootsCoreUnit::MassG).unwrap_err(); + let q = common::qty("1", Unit::Each); + let err = q.try_convert_to(Unit::MassG).unwrap_err(); assert_eq!( err, radroots_core::RadrootsCoreUnitConvertError::NotConvertibleUnits { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG } ); } #[test] fn try_convert_to_same_unit_returns_self_clone() { - let q = common::qty("2", RadrootsCoreUnit::MassG).with_label("x"); - let converted = q - .try_convert_to(RadrootsCoreUnit::MassG) - .expect("same unit"); + let q = common::qty("2", Unit::MassG).with_label("x"); + let converted = q.try_convert_to(Unit::MassG).expect("same unit"); assert_eq!(converted, q); } diff --git a/crates/core/tests/quantity_price.rs b/crates/core/tests/quantity_price.rs @@ -1,37 +1,27 @@ mod common; use radroots_core::{ - RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps, - RadrootsCoreUnit, + QuantityPrice, RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps, Unit, }; #[test] fn cost_for_scales_by_ratio() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); - let cost = price.cost_for(&common::qty("2", RadrootsCoreUnit::MassKg)); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg)); + let cost = price.cost_for(&common::qty("2", Unit::MassKg)); assert_eq!(cost.amount, common::dec("20")); } #[test] fn cost_for_returns_zero_on_unit_mismatch() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); - let cost = price.cost_for(&common::qty("1", RadrootsCoreUnit::Each)); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg)); + let cost = price.cost_for(&common::qty("1", Unit::Each)); assert!(cost.amount.is_zero()); } #[test] fn cost_for_rounded_and_quantized_price_differ() { - let price = RadrootsCoreQuantityPrice::new( - common::money("1.005", "USD"), - common::qty("1", RadrootsCoreUnit::Each), - ); - let qty = common::qty("2", RadrootsCoreUnit::Each); + let price = QuantityPrice::new(common::money("1.005", "USD"), common::qty("1", Unit::Each)); + let qty = common::qty("2", Unit::Each); let rounded = price.cost_for_rounded(&qty); let quantized = price.cost_for_with_quantized_price(&qty); @@ -41,165 +31,127 @@ fn cost_for_rounded_and_quantized_price_differ() { #[test] fn try_cost_for_validates_quantity_and_units() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::Each), - ); - let zero_price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("0", RadrootsCoreUnit::Each), - ); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::Each)); + let zero_price = QuantityPrice::new(common::money("10", "USD"), common::qty("0", Unit::Each)); assert_eq!( - zero_price.try_cost_for(&common::qty("1", RadrootsCoreUnit::Each)), + zero_price.try_cost_for(&common::qty("1", Unit::Each)), Err(RadrootsCoreQuantityPriceError::PerQuantityZero) ); assert_eq!( - price.try_cost_for(&common::qty("1", RadrootsCoreUnit::MassKg)), + price.try_cost_for(&common::qty("1", Unit::MassKg)), Err(RadrootsCoreQuantityPriceError::UnitMismatch { - have: RadrootsCoreUnit::MassKg, - want: RadrootsCoreUnit::Each + have: Unit::MassKg, + want: Unit::Each }) ); } #[test] fn try_cost_for_rounded_error_path_is_exercised() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::Each), - ); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::Each)); assert_eq!( - price.try_cost_for_rounded(&common::qty("1", RadrootsCoreUnit::MassKg)), + price.try_cost_for_rounded(&common::qty("1", Unit::MassKg)), Err(RadrootsCoreQuantityPriceError::UnitMismatch { - have: RadrootsCoreUnit::MassKg, - want: RadrootsCoreUnit::Each + have: Unit::MassKg, + want: Unit::Each }) ); } #[test] fn try_cost_for_amount_in_converts_mass_units() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg)); let cost = price - .try_cost_for_amount_in(common::dec("500"), RadrootsCoreUnit::MassG) + .try_cost_for_amount_in(common::dec("500"), Unit::MassG) .unwrap(); assert_eq!(cost.amount, common::dec("5")); } #[test] fn try_cost_for_amount_in_converts_volume_units() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::VolumeL), - ); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::VolumeL)); let cost = price - .try_cost_for_amount_in(common::dec("500"), RadrootsCoreUnit::VolumeMl) + .try_cost_for_amount_in(common::dec("500"), Unit::VolumeMl) .unwrap(); assert_eq!(cost.amount, common::dec("5")); } #[test] fn try_cost_for_amount_in_rejects_non_convertible_units() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg)); assert_eq!( - price.try_cost_for_amount_in(common::dec("1"), RadrootsCoreUnit::Each), + price.try_cost_for_amount_in(common::dec("1"), Unit::Each), Err(RadrootsCoreQuantityPriceError::NonConvertibleUnits { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassKg + from: Unit::Each, + to: Unit::MassKg }) ); } #[test] fn try_cost_for_amount_in_same_unit_path_is_exercised() { - let price = RadrootsCoreQuantityPrice::new( - common::money("4", "USD"), - common::qty("1", RadrootsCoreUnit::Each), - ); + let price = QuantityPrice::new(common::money("4", "USD"), common::qty("1", Unit::Each)); let out = price - .try_cost_for_amount_in(common::dec("3"), RadrootsCoreUnit::Each) + .try_cost_for_amount_in(common::dec("3"), Unit::Each) .unwrap(); assert_eq!(out.amount, common::dec("12")); } #[test] fn try_cost_for_quantity_in_path_is_exercised() { - let price = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); - let qty = common::qty("250", RadrootsCoreUnit::MassG); + let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg)); + let qty = common::qty("250", Unit::MassG); let out = price.try_cost_for_quantity_in(&qty).unwrap(); assert_eq!(out.amount, common::dec("2.5")); } #[test] fn try_to_unit_price_error_and_same_unit_paths_are_exercised() { - let zero = RadrootsCoreQuantityPrice::new( - common::money("10", "USD"), - common::qty("0", RadrootsCoreUnit::MassKg), - ); + let zero = QuantityPrice::new(common::money("10", "USD"), common::qty("0", Unit::MassKg)); assert_eq!( - zero.try_to_unit_price(RadrootsCoreUnit::MassG), + zero.try_to_unit_price(Unit::MassG), Err(RadrootsCoreQuantityPriceError::PerQuantityZero) ); - let base = RadrootsCoreQuantityPrice::new( - common::money("5", "USD"), - common::qty("2", RadrootsCoreUnit::MassKg), - ); - let same = base.try_to_unit_price(RadrootsCoreUnit::MassKg).unwrap(); - assert_eq!(same.quantity.unit, RadrootsCoreUnit::MassKg); + let base = QuantityPrice::new(common::money("5", "USD"), common::qty("2", Unit::MassKg)); + let same = base.try_to_unit_price(Unit::MassKg).unwrap(); + assert_eq!(same.quantity.unit, Unit::MassKg); assert_eq!(same.quantity.amount, common::dec("1")); assert_eq!(same.amount.amount, common::dec("2.5")); - let err = base - .try_to_unit_price(RadrootsCoreUnit::VolumeMl) - .unwrap_err(); + let err = base.try_to_unit_price(Unit::VolumeMl).unwrap_err(); assert_eq!( err, RadrootsCoreQuantityPriceError::NonConvertibleUnits { - from: RadrootsCoreUnit::MassKg, - to: RadrootsCoreUnit::VolumeMl + from: Unit::MassKg, + to: Unit::VolumeMl } ); } #[test] fn cost_for_and_quantized_price_zero_paths_are_exercised() { - let p = RadrootsCoreQuantityPrice::new( - common::money("3.33", "USD"), - common::qty("1", RadrootsCoreUnit::Each), - ); - let zero_qty = common::qty("0", RadrootsCoreUnit::Each); + let p = QuantityPrice::new(common::money("3.33", "USD"), common::qty("1", Unit::Each)); + let zero_qty = common::qty("0", Unit::Each); assert!(p.cost_for(&zero_qty).amount.is_zero()); assert!(p.cost_for_with_quantized_price(&zero_qty).amount.is_zero()); - let zero_per = RadrootsCoreQuantityPrice::new( - common::money("3.33", "USD"), - common::qty("0", RadrootsCoreUnit::Each), - ); + let zero_per = QuantityPrice::new(common::money("3.33", "USD"), common::qty("0", Unit::Each)); assert!( zero_per - .cost_for(&common::qty("1", RadrootsCoreUnit::Each)) + .cost_for(&common::qty("1", Unit::Each)) .amount .is_zero() ); assert!( zero_per - .cost_for_with_quantized_price(&common::qty("1", RadrootsCoreUnit::Each)) + .cost_for_with_quantized_price(&common::qty("1", Unit::Each)) .amount .is_zero() ); - let mismatch_qty = common::qty("1", RadrootsCoreUnit::MassG); + let mismatch_qty = common::qty("1", Unit::MassG); assert!( p.cost_for_with_quantized_price(&mismatch_qty) .amount @@ -209,24 +161,19 @@ fn cost_for_and_quantized_price_zero_paths_are_exercised() { #[test] fn try_to_unit_price_detects_underflow_to_zero_normalized_amount() { - let tiny = RadrootsCoreQuantityPrice::new( + let tiny = QuantityPrice::new( common::money("1", "USD"), - common::qty("0.0000000000000000000000000001", RadrootsCoreUnit::VolumeMl), + common::qty("0.0000000000000000000000000001", Unit::VolumeMl), ); - let err = tiny - .try_to_unit_price(RadrootsCoreUnit::VolumeL) - .unwrap_err(); + let err = tiny.try_to_unit_price(Unit::VolumeL).unwrap_err(); assert_eq!(err, RadrootsCoreQuantityPriceError::PerQuantityZero); } #[test] fn try_to_canonical_unit_price_converts_units() { - let price = RadrootsCoreQuantityPrice::new( - common::money("6.99", "USD"), - common::qty("1", RadrootsCoreUnit::MassLb), - ); + let price = QuantityPrice::new(common::money("6.99", "USD"), common::qty("1", Unit::MassLb)); let canonical = price.try_to_canonical_unit_price().unwrap(); - assert_eq!(canonical.quantity.unit, RadrootsCoreUnit::MassG); + assert_eq!(canonical.quantity.unit, Unit::MassG); assert_eq!(canonical.quantity.amount, common::dec("1")); let expected = common::dec("6.99") / common::dec("453.59237"); assert_eq!(canonical.amount.amount, expected); @@ -234,15 +181,9 @@ fn try_to_canonical_unit_price_converts_units() { #[test] fn is_price_per_canonical_unit_detects_canonical() { - let price = RadrootsCoreQuantityPrice::new( - common::money("1.00", "USD"), - common::qty("1", RadrootsCoreUnit::MassG), - ); + let price = QuantityPrice::new(common::money("1.00", "USD"), common::qty("1", Unit::MassG)); assert!(price.is_price_per_canonical_unit()); - let price = RadrootsCoreQuantityPrice::new( - common::money("1.00", "USD"), - common::qty("1", RadrootsCoreUnit::MassKg), - ); + let price = QuantityPrice::new(common::money("1.00", "USD"), common::qty("1", Unit::MassKg)); assert!(!price.is_price_per_canonical_unit()); } diff --git a/crates/core/tests/serde.rs b/crates/core/tests/serde.rs @@ -4,10 +4,7 @@ mod common; use core::str::FromStr; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCorePercent, - RadrootsCoreQuantity, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Percent, Quantity, Unit}; use serde_json::Value; #[test] @@ -16,13 +13,13 @@ fn decimal_serializes_as_string() { let json = serde_json::to_string(&d).unwrap(); assert_eq!(json, "\"1.23\""); - let back: RadrootsCoreDecimal = serde_json::from_str(&json).unwrap(); + let back: Decimal = serde_json::from_str(&json).unwrap(); assert_eq!(back, common::dec("1.23")); } #[test] fn quantity_uses_decimal_str_and_omits_empty_label() { - let q = RadrootsCoreQuantity::new(common::dec("1.2300"), RadrootsCoreUnit::MassKg); + let q = Quantity::new(common::dec("1.2300"), Unit::MassKg); let value = serde_json::to_value(&q).unwrap(); assert_eq!(value["amount"], Value::String("1.23".to_string())); @@ -33,34 +30,34 @@ fn quantity_uses_decimal_str_and_omits_empty_label() { #[test] fn quantity_deserializes_decimal_str_via_serde_ext() { let raw = r#"{"amount":"1.2300","unit":"kg","label":"bag"}"#; - let q: RadrootsCoreQuantity = serde_json::from_str(raw).unwrap(); + let q: Quantity = serde_json::from_str(raw).unwrap(); assert_eq!(q.amount, common::dec("1.23")); - assert_eq!(q.unit, RadrootsCoreUnit::MassKg); + assert_eq!(q.unit, Unit::MassKg); assert_eq!(q.label.as_deref(), Some("bag")); } #[test] fn quantity_rejects_non_string_decimal_amount() { let raw = r#"{"amount":1.23,"unit":"kg"}"#; - let err = serde_json::from_str::<RadrootsCoreQuantity>(raw).unwrap_err(); + let err = serde_json::from_str::<Quantity>(raw).unwrap_err(); assert!(err.to_string().contains("invalid type")); } #[test] fn money_and_percent_roundtrip_with_strings() { - let money = RadrootsCoreMoney::new(common::dec("2.50"), RadrootsCoreCurrency::USD); + let money = Money::new(common::dec("2.50"), Currency::USD); let value = serde_json::to_value(&money).unwrap(); assert_eq!(value["amount"], Value::String("2.5".to_string())); assert_eq!(value["currency"], Value::String("USD".to_string())); - let pct = RadrootsCorePercent::new(common::dec("12.5")); + let pct = Percent::new(common::dec("12.5")); let value = serde_json::to_value(&pct).unwrap(); assert_eq!(value["value"], Value::String("12.5".to_string())); } #[test] fn currency_serializes_as_code() { - let c = RadrootsCoreCurrency::from_str("usd").unwrap(); + let c = Currency::from_str("usd").unwrap(); let json = serde_json::to_string(&c).unwrap(); assert_eq!(json, "\"USD\""); } diff --git a/crates/core/tests/unit.rs b/crates/core/tests/unit.rs @@ -3,14 +3,13 @@ mod common; use core::str::FromStr; use radroots_core::{ - RadrootsCoreUnit, RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError, - convert_mass_decimal, convert_unit_decimal, convert_volume_decimal, parse_mass_unit, - parse_volume_unit, + RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError, Unit, convert_mass_decimal, + convert_unit_decimal, convert_volume_decimal, parse_mass_unit, parse_volume_unit, }; #[test] fn parses_units_and_synonyms() { - use RadrootsCoreUnit::*; + use Unit::*; let cases = [ ("each", Each), ("ea", Each), @@ -29,31 +28,31 @@ fn parses_units_and_synonyms() { ("milliliters", VolumeMl), ]; for (input, expected) in cases { - assert_eq!(RadrootsCoreUnit::from_str(input).unwrap(), expected); + assert_eq!(Unit::from_str(input).unwrap(), expected); } } #[test] fn rejects_unknown_units() { assert_eq!( - RadrootsCoreUnit::from_str("unknown"), + Unit::from_str("unknown"), Err(RadrootsCoreUnitParseError::UnknownUnit) ); } #[test] fn same_dimension_matches_mass_and_volume_groups() { - use RadrootsCoreUnit::*; - assert!(RadrootsCoreUnit::same_dimension(MassKg, MassG)); - assert!(RadrootsCoreUnit::same_dimension(VolumeL, VolumeMl)); - assert!(RadrootsCoreUnit::same_dimension(Each, Each)); - assert!(!RadrootsCoreUnit::same_dimension(MassKg, VolumeL)); - assert!(!RadrootsCoreUnit::same_dimension(Each, MassG)); + use Unit::*; + assert!(Unit::same_dimension(MassKg, MassG)); + assert!(Unit::same_dimension(VolumeL, VolumeMl)); + assert!(Unit::same_dimension(Each, Each)); + assert!(!Unit::same_dimension(MassKg, VolumeL)); + assert!(!Unit::same_dimension(Each, MassG)); } #[test] fn canonical_unit_maps_dimensions() { - use RadrootsCoreUnit::*; + use Unit::*; assert_eq!(Each.canonical_unit(), Each); assert_eq!(MassKg.canonical_unit(), MassG); assert_eq!(MassLb.canonical_unit(), MassG); @@ -62,7 +61,7 @@ fn canonical_unit_maps_dimensions() { #[test] fn code_and_predicate_helpers_cover_all_variants() { - use RadrootsCoreUnit::*; + use Unit::*; assert_eq!(Each.code(), "each"); assert_eq!(MassKg.code(), "kg"); assert_eq!(MassG.code(), "g"); @@ -81,7 +80,7 @@ fn code_and_predicate_helpers_cover_all_variants() { #[test] fn parse_mass_unit_enforces_mass_only() { - assert_eq!(parse_mass_unit("kg"), Ok(RadrootsCoreUnit::MassKg)); + assert_eq!(parse_mass_unit("kg"), Ok(Unit::MassKg)); assert_eq!( parse_mass_unit("each"), Err(RadrootsCoreUnitParseError::NotAMassUnit) @@ -94,7 +93,7 @@ fn parse_mass_unit_enforces_mass_only() { #[test] fn parse_volume_unit_enforces_volume_only() { - assert_eq!(parse_volume_unit("l"), Ok(RadrootsCoreUnit::VolumeL)); + assert_eq!(parse_volume_unit("l"), Ok(Unit::VolumeL)); assert_eq!( parse_volume_unit("kg"), Err(RadrootsCoreUnitParseError::NotAVolumeUnit) @@ -107,7 +106,7 @@ fn parse_volume_unit_enforces_volume_only() { #[test] fn convert_mass_decimal_converts_between_mass_units() { - use RadrootsCoreUnit::*; + use Unit::*; let kg_to_g = convert_mass_decimal(common::dec("1"), MassKg, MassG).unwrap(); let g_to_kg = convert_mass_decimal(common::dec("1000"), MassG, MassKg).unwrap(); let lb_to_g = convert_mass_decimal(common::dec("1"), MassLb, MassG).unwrap(); @@ -125,38 +124,28 @@ fn convert_mass_decimal_converts_between_mass_units() { #[test] fn convert_mass_decimal_rejects_non_mass_units() { - let err = convert_mass_decimal( - common::dec("1"), - RadrootsCoreUnit::Each, - RadrootsCoreUnit::MassG, - ) - .unwrap_err(); + let err = convert_mass_decimal(common::dec("1"), Unit::Each, Unit::MassG).unwrap_err(); assert_eq!( err, RadrootsCoreUnitConvertError::NotMassUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG } ); - let err = convert_mass_decimal( - common::dec("1"), - RadrootsCoreUnit::MassKg, - RadrootsCoreUnit::Each, - ) - .unwrap_err(); + let err = convert_mass_decimal(common::dec("1"), Unit::MassKg, Unit::Each).unwrap_err(); assert_eq!( err, RadrootsCoreUnitConvertError::NotMassUnit { - from: RadrootsCoreUnit::MassKg, - to: RadrootsCoreUnit::Each + from: Unit::MassKg, + to: Unit::Each } ); } #[test] fn convert_volume_decimal_converts_between_volume_units() { - use RadrootsCoreUnit::*; + use Unit::*; let l_to_ml = convert_volume_decimal(common::dec("1"), VolumeL, VolumeMl).unwrap(); let ml_to_l = convert_volume_decimal(common::dec("250"), VolumeMl, VolumeL).unwrap(); assert_eq!(l_to_ml, common::dec("1000")); @@ -165,38 +154,28 @@ fn convert_volume_decimal_converts_between_volume_units() { #[test] fn convert_volume_decimal_rejects_non_volume_units() { - let err = convert_volume_decimal( - common::dec("1"), - RadrootsCoreUnit::Each, - RadrootsCoreUnit::VolumeMl, - ) - .unwrap_err(); + let err = convert_volume_decimal(common::dec("1"), Unit::Each, Unit::VolumeMl).unwrap_err(); assert_eq!( err, RadrootsCoreUnitConvertError::NotVolumeUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::VolumeMl + from: Unit::Each, + to: Unit::VolumeMl } ); - let err = convert_volume_decimal( - common::dec("1"), - RadrootsCoreUnit::VolumeMl, - RadrootsCoreUnit::Each, - ) - .unwrap_err(); + let err = convert_volume_decimal(common::dec("1"), Unit::VolumeMl, Unit::Each).unwrap_err(); assert_eq!( err, RadrootsCoreUnitConvertError::NotVolumeUnit { - from: RadrootsCoreUnit::VolumeMl, - to: RadrootsCoreUnit::Each + from: Unit::VolumeMl, + to: Unit::Each } ); } #[test] fn convert_unit_decimal_converts_matching_dimensions() { - use RadrootsCoreUnit::*; + use Unit::*; let kg_to_g = convert_unit_decimal(common::dec("1"), MassKg, MassG).unwrap(); let l_to_ml = convert_unit_decimal(common::dec("2"), VolumeL, VolumeMl).unwrap(); let each_to_each = convert_unit_decimal(common::dec("3"), Each, Each).unwrap(); @@ -207,7 +186,7 @@ fn convert_unit_decimal_converts_matching_dimensions() { #[test] fn display_paths_for_unit_and_errors_are_exercised() { - assert_eq!(RadrootsCoreUnit::MassOz.to_string(), "oz"); + assert_eq!(Unit::MassOz.to_string(), "oz"); assert_eq!( RadrootsCoreUnitParseError::UnknownUnit.to_string(), "unknown unit string" @@ -222,24 +201,24 @@ fn display_paths_for_unit_and_errors_are_exercised() { ); assert_eq!( RadrootsCoreUnitConvertError::NotMassUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG } .to_string(), "unit conversion requires mass units: each -> g" ); assert_eq!( RadrootsCoreUnitConvertError::NotVolumeUnit { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::VolumeL + from: Unit::Each, + to: Unit::VolumeL } .to_string(), "unit conversion requires volume units: each -> l" ); assert_eq!( RadrootsCoreUnitConvertError::NotConvertibleUnits { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG } .to_string(), "unit conversion requires matching dimensions: each -> g" @@ -248,17 +227,12 @@ fn display_paths_for_unit_and_errors_are_exercised() { #[test] fn convert_unit_decimal_rejects_mismatched_dimensions() { - let err = convert_unit_decimal( - common::dec("1"), - RadrootsCoreUnit::Each, - RadrootsCoreUnit::MassG, - ) - .unwrap_err(); + let err = convert_unit_decimal(common::dec("1"), Unit::Each, Unit::MassG).unwrap_err(); assert_eq!( err, RadrootsCoreUnitConvertError::NotConvertibleUnits { - from: RadrootsCoreUnit::Each, - to: RadrootsCoreUnit::MassG + from: Unit::Each, + to: Unit::MassG } ); } @@ -266,19 +240,19 @@ fn convert_unit_decimal_rejects_mismatched_dimensions() { #[cfg(feature = "serde")] #[test] fn serde_roundtrip_for_unit_paths() { - use RadrootsCoreUnit::*; + use Unit::*; let all = [Each, MassKg, MassG, MassOz, MassLb, VolumeL, VolumeMl]; for unit in all { let json = serde_json::to_string(&unit).unwrap(); - let back: RadrootsCoreUnit = serde_json::from_str(&json).unwrap(); + let back: Unit = serde_json::from_str(&json).unwrap(); assert_eq!(back, unit); } - let back: RadrootsCoreUnit = serde_json::from_str("\"kg\"").unwrap(); - assert_eq!(back, RadrootsCoreUnit::MassKg); - let unknown_err = serde_json::from_str::<RadrootsCoreUnit>("\"bogus\"").unwrap_err(); + let back: Unit = serde_json::from_str("\"kg\"").unwrap(); + assert_eq!(back, Unit::MassKg); + let unknown_err = serde_json::from_str::<Unit>("\"bogus\"").unwrap_err(); assert!(unknown_err.to_string().contains("unknown unit")); - let err = serde_json::from_str::<RadrootsCoreUnit>("123").unwrap_err(); + let err = serde_json::from_str::<Unit>("123").unwrap_err(); assert!(err.to_string().contains("invalid type")); - let missing_err = serde_json::from_str::<RadrootsCoreUnit>("{}").unwrap_err(); + let missing_err = serde_json::from_str::<Unit>("{}").unwrap_err(); assert!(!missing_err.to_string().is_empty()); } diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json @@ -157,6 +157,55 @@ "hash_algorithm": "sha256_bytes_v1" }, { + "role": "core_currency_value_authority", + "path": "crates/core/src/currency.rs", + "byte_length": 4095, + "sha256": "fdd7ccb88abdf7b91a362fdeeded752346331c5510ed1c769ec69a2780fa333a", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_decimal_value_authority", + "path": "crates/core/src/decimal.rs", + "byte_length": 3851, + "sha256": "741c16a8cad7a769a7cf74458ef56141f04badb62de107966a9be85d241faa39", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_money_value_authority", + "path": "crates/core/src/money.rs", + "byte_length": 6615, + "sha256": "8e830a3a01a2252d06275306848bf2389b5e4fb6a88bb85015bc6a072da969d0", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_percent_value_authority", + "path": "crates/core/src/percent.rs", + "byte_length": 2266, + "sha256": "f0d7d9c66734666070620e95b9d46a22f5b21b881aa93a9a8b914fb2b5f86d69", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_quantity_value_authority", + "path": "crates/core/src/quantity.rs", + "byte_length": 6189, + "sha256": "9b5450b602f6da4bf85e644bc362cec777638b4a95b6d4d4af791ec9f501d3ae", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_quantity_price_value_authority", + "path": "crates/core/src/quantity_price.rs", + "byte_length": 5579, + "sha256": "166a3a6a43733dcafc39f917f166710fa24f408dbaf1c5133f9a51e5732d6d20", + "hash_algorithm": "sha256_bytes_v1" + }, + { + "role": "core_unit_value_authority", + "path": "crates/core/src/unit.rs", + "byte_length": 9280, + "sha256": "736239f31fd0864fd1c1862aa6df49d56d4d0d8991ef6e7a25d83ab1b99a5896", + "hash_algorithm": "sha256_bytes_v1" + }, + { "role": "event_store_error_and_limits", "path": "crates/event_store/src/error.rs", "byte_length": 19421, @@ -236,8 +285,8 @@ { "role": "predecessor_successor_governance", "path": "tools/xtask/src/contract/food_availability_projection.rs", - "byte_length": 195036, - "sha256": "d3c567ea3b4ea709723af2d5b6a2dc47993c6ba42c1d62895bd26fa101ebf829", + "byte_length": 194594, + "sha256": "4b2961fc69c8204ad5470c0e51c93533063e49d7a14da00a53598c3e8d0cdf6b", "hash_algorithm": "sha256_bytes_v1" }, { @@ -250,15 +299,15 @@ { "role": "source_maintenance_governance", "path": "tools/xtask/src/contract/source_maintenance.rs", - "byte_length": 176811, - "sha256": "c9acc9934b3ce89505046049d4c125f4fde7d03224ffeeb0f22d10c7b22e1774", + "byte_length": 177996, + "sha256": "c309da38f12101d77c4de1caa49ef443252cac6238b49692182851c2cf894bda", "hash_algorithm": "sha256_bytes_v1" }, { "role": "contract_command_authority", "path": "tools/xtask/src/contract.rs", - "byte_length": 500480, - "sha256": "84739ad8b78bae49ea1f992672101bd83135415042cb22a85dbcbf5c878fc097", + "byte_length": 500241, + "sha256": "972e2a6b1099267e11d9a34e73fd38b20fc217a3dd79bd9a0f1f060b680ba2ba", "hash_algorithm": "sha256_bytes_v1" }, { diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.sha256 b/crates/event_store/contracts/source_maintenance_v1.manifest.sha256 @@ -1 +1 @@ -004b0c8568e113063732c8f906673a38520717726ff22af7b4530780dbef3a08 +32670d9ee4b33052c41ce808b3d9ec8471dd67919ed415ce4f68ce9408ead11c diff --git a/crates/event_store/src/generated/source_maintenance_manifest.rs b/crates/event_store/src/generated/source_maintenance_manifest.rs @@ -1,8 +1,8 @@ // @generated by `cargo xtask contract source-maintenance-manifest --write`; do not edit. -pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 11546,\n \"sha256\": \"0378afe281a46e02e83e128324efefc3da950abf3e1d3d06c75b628f69810bd2\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 195036,\n \"sha256\": \"d3c567ea3b4ea709723af2d5b6a2dc47993c6ba42c1d62895bd26fa101ebf829\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 835585,\n \"sha256\": \"51d524cb00d2d96dc0da35fc9133c54f2e5328a830d56b8d5e83a7f1e9654ddc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 176811,\n \"sha256\": \"c9acc9934b3ce89505046049d4c125f4fde7d03224ffeeb0f22d10c7b22e1774\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 500480,\n \"sha256\": \"84739ad8b78bae49ea1f992672101bd83135415042cb22a85dbcbf5c878fc097\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 14976,\n \"sha256\": \"42b59a8576e4f86b091941a64349ac9eaa20b1e3b52aef367bb3d5947d8ccd1d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n"; -pub(crate) const SOURCE_MAINTENANCE_MANIFEST_BYTE_LENGTH: usize = 14459; +pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 11546,\n \"sha256\": \"0378afe281a46e02e83e128324efefc3da950abf3e1d3d06c75b628f69810bd2\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_currency_value_authority\",\n \"path\": \"crates/core/src/currency.rs\",\n \"byte_length\": 4095,\n \"sha256\": \"fdd7ccb88abdf7b91a362fdeeded752346331c5510ed1c769ec69a2780fa333a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_decimal_value_authority\",\n \"path\": \"crates/core/src/decimal.rs\",\n \"byte_length\": 3851,\n \"sha256\": \"741c16a8cad7a769a7cf74458ef56141f04badb62de107966a9be85d241faa39\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_money_value_authority\",\n \"path\": \"crates/core/src/money.rs\",\n \"byte_length\": 6615,\n \"sha256\": \"8e830a3a01a2252d06275306848bf2389b5e4fb6a88bb85015bc6a072da969d0\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_percent_value_authority\",\n \"path\": \"crates/core/src/percent.rs\",\n \"byte_length\": 2266,\n \"sha256\": \"f0d7d9c66734666070620e95b9d46a22f5b21b881aa93a9a8b914fb2b5f86d69\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_value_authority\",\n \"path\": \"crates/core/src/quantity.rs\",\n \"byte_length\": 6189,\n \"sha256\": \"9b5450b602f6da4bf85e644bc362cec777638b4a95b6d4d4af791ec9f501d3ae\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_price_value_authority\",\n \"path\": \"crates/core/src/quantity_price.rs\",\n \"byte_length\": 5579,\n \"sha256\": \"166a3a6a43733dcafc39f917f166710fa24f408dbaf1c5133f9a51e5732d6d20\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_unit_value_authority\",\n \"path\": \"crates/core/src/unit.rs\",\n \"byte_length\": 9280,\n \"sha256\": \"736239f31fd0864fd1c1862aa6df49d56d4d0d8991ef6e7a25d83ab1b99a5896\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 194594,\n \"sha256\": \"4b2961fc69c8204ad5470c0e51c93533063e49d7a14da00a53598c3e8d0cdf6b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 835585,\n \"sha256\": \"51d524cb00d2d96dc0da35fc9133c54f2e5328a830d56b8d5e83a7f1e9654ddc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 177996,\n \"sha256\": \"c309da38f12101d77c4de1caa49ef443252cac6238b49692182851c2cf894bda\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 500241,\n \"sha256\": \"972e2a6b1099267e11d9a34e73fd38b20fc217a3dd79bd9a0f1f060b680ba2ba\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 14976,\n \"sha256\": \"42b59a8576e4f86b091941a64349ac9eaa20b1e3b52aef367bb3d5947d8ccd1d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n"; +pub(crate) const SOURCE_MAINTENANCE_MANIFEST_BYTE_LENGTH: usize = 16259; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str = - "004b0c8568e113063732c8f906673a38520717726ff22af7b4530780dbef3a08"; + "32670d9ee4b33052c41ce808b3d9ec8471dd67919ed415ce4f68ce9408ead11c"; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SCHEMA_VERSION: u32 = 1; pub(crate) const SOURCE_MAINTENANCE_CONTRACT_ID: &str = "radroots_event_store.source_maintenance_v1"; diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -8086,7 +8086,7 @@ fn validate_required_coverage_summary_with_policy( Ok(()) } -const CORE_UNIT_DIMENSION_ENUM: &str = "RadrootsCoreUnitDimension"; +const CORE_UNIT_DIMENSION_ENUM: &str = "UnitDimension"; const CORE_UNIT_DIMENSION_ORDER: [&str; 3] = ["Count", "Mass", "Volume"]; fn extract_enum_body<'a>(source: &'a str, enum_name: &str) -> Result<&'a str, String> { @@ -9414,7 +9414,7 @@ version = "1.0.0" ); write_file( &root.join("crates").join("core").join("src").join("unit.rs"), - r#"pub enum RadrootsCoreUnitDimension { + r#"pub enum UnitDimension { Count, Mass, Volume, @@ -11208,13 +11208,13 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"] #[test] fn parses_enum_variants_in_declared_order() { let source = r#" -pub enum RadrootsCoreUnitDimension { +pub enum UnitDimension { Count, Mass, Volume, } "#; - let enum_body = extract_enum_body(source, "RadrootsCoreUnitDimension").expect("enum body"); + let enum_body = extract_enum_body(source, "UnitDimension").expect("enum body"); let variants = parse_enum_variants(enum_body); assert_eq!(variants, vec!["Count", "Mass", "Volume"]); } @@ -11222,13 +11222,13 @@ pub enum RadrootsCoreUnitDimension { #[test] fn fails_when_enum_order_does_not_match_contract() { let source = r#" -pub enum RadrootsCoreUnitDimension { +pub enum UnitDimension { Mass, Count, Volume, } "#; - let enum_body = extract_enum_body(source, "RadrootsCoreUnitDimension").expect("enum body"); + let enum_body = extract_enum_body(source, "UnitDimension").expect("enum body"); let variants = parse_enum_variants(enum_body); let expected = CORE_UNIT_DIMENSION_ORDER .iter() @@ -11898,22 +11898,17 @@ members = ["crates/a", "crates/b"] #[test] fn enum_extract_and_parse_error_paths_are_reported() { - let missing = extract_enum_body("pub struct X;", "RadrootsCoreUnitDimension") - .expect_err("missing enum"); + let missing = + extract_enum_body("pub struct X;", "UnitDimension").expect_err("missing enum"); assert!(missing.contains("missing enum")); - let missing_brace = extract_enum_body( - "pub enum RadrootsCoreUnitDimension", - "RadrootsCoreUnitDimension", - ) - .expect_err("missing opening brace"); + let missing_brace = extract_enum_body("pub enum UnitDimension", "UnitDimension") + .expect_err("missing opening brace"); assert!(missing_brace.contains("missing opening brace")); - let missing_close = extract_enum_body( - "pub enum RadrootsCoreUnitDimension { Count, Mass", - "RadrootsCoreUnitDimension", - ) - .expect_err("missing closing brace"); + let missing_close = + extract_enum_body("pub enum UnitDimension { Count, Mass", "UnitDimension") + .expect_err("missing closing brace"); assert!(missing_close.contains("missing closing brace")); let variants = parse_enum_variants( @@ -11928,8 +11923,8 @@ members = ["crates/a", "crates/b"] assert_eq!(variants, vec!["Count".to_string()]); let nested = extract_enum_body( - "pub enum RadrootsCoreUnitDimension { Count = { 1 }, Mass = 2 }", - "RadrootsCoreUnitDimension", + "pub enum UnitDimension { Count = { 1 }, Mass = 2 }", + "UnitDimension", ) .expect("nested braces in enum body"); assert!(nested.contains("Count")); @@ -13631,7 +13626,7 @@ requires_release_notes = true let bundle = load_contract_bundle(&root).expect("load bundle"); write_file( &root.join("crates").join("core").join("src").join("unit.rs"), - r#"pub enum RadrootsCoreUnitDimension { + r#"pub enum UnitDimension { Mass, Count, Volume, @@ -13643,7 +13638,7 @@ Volume, write_file( &root.join("crates").join("core").join("src").join("unit.rs"), - r#"pub enum RadrootsCoreUnitDimension { + r#"pub enum UnitDimension { Count, Mass, Volume, @@ -13794,7 +13789,7 @@ readme = { workspace = true } .join("core") .join("src") .join("unit.rs"), - r#"pub enum RadrootsCoreUnitDimension { + r#"pub enum UnitDimension { Mass, Count, Volume, diff --git a/tools/xtask/src/contract/food_availability_projection.rs b/tools/xtask/src/contract/food_availability_projection.rs @@ -3996,18 +3996,8 @@ mod tests { #[test] fn downstream_nip09_only_supersession_is_transitively_validated() { - const SOURCE_MAINTENANCE_SUPERSEDED_PATHS: &[&str] = &[ - "Cargo.toml", - "crates/event_store/src/error.rs", - "crates/event_store/src/generated.rs", - "crates/event_store/src/lib.rs", - "crates/event_store/src/migrations.rs", - "crates/event_store/src/model.rs", - "crates/event_store/src/nip09/reconciliation_v1.rs", - "crates/event_store/src/schema.rs", - "crates/event_store/src/store.rs", - "crates/event_store/src/store/protocol_reconciliation_v1.rs", - ]; + let source_maintenance_superseded_paths = + super::super::source_maintenance::predecessor_superseded_source_paths(); let root = repository_root(); let (food_paths, nip09_paths) = partition_downstream_predecessor_supersessions( @@ -4028,11 +4018,11 @@ mod tests { ); validate_food_availability_projection_predecessor_production_sources_under_lock( &root, - SOURCE_MAINTENANCE_SUPERSEDED_PATHS, + source_maintenance_superseded_paths, ) .expect("Food and transitive NIP-09 successor source coverage"); - let mut duplicate = SOURCE_MAINTENANCE_SUPERSEDED_PATHS.to_vec(); + let mut duplicate = source_maintenance_superseded_paths.to_vec(); duplicate.push("crates/event_store/src/store/protocol_reconciliation_v1.rs"); let error = validate_food_availability_projection_predecessor_production_sources_under_lock( @@ -4041,7 +4031,7 @@ mod tests { .expect_err("duplicate transitive supersession must fail"); assert!(error.contains("must be unique"), "{error}"); - let mut unknown = SOURCE_MAINTENANCE_SUPERSEDED_PATHS.to_vec(); + let mut unknown = source_maintenance_superseded_paths.to_vec(); unknown.push("crates/event_store/src/store/not_predecessor_bound.rs"); let error = validate_food_availability_projection_predecessor_production_sources_under_lock( diff --git a/tools/xtask/src/contract/source_maintenance.rs b/tools/xtask/src/contract/source_maintenance.rs @@ -247,6 +247,34 @@ const SOURCE_SPECS: &[SourceSpec] = &[ path: "Cargo.toml", }, SourceSpec { + role: "core_currency_value_authority", + path: "crates/core/src/currency.rs", + }, + SourceSpec { + role: "core_decimal_value_authority", + path: "crates/core/src/decimal.rs", + }, + SourceSpec { + role: "core_money_value_authority", + path: "crates/core/src/money.rs", + }, + SourceSpec { + role: "core_percent_value_authority", + path: "crates/core/src/percent.rs", + }, + SourceSpec { + role: "core_quantity_value_authority", + path: "crates/core/src/quantity.rs", + }, + SourceSpec { + role: "core_quantity_price_value_authority", + path: "crates/core/src/quantity_price.rs", + }, + SourceSpec { + role: "core_unit_value_authority", + path: "crates/core/src/unit.rs", + }, + SourceSpec { role: "event_store_error_and_limits", path: "crates/event_store/src/error.rs", }, @@ -319,6 +347,13 @@ pub(super) fn source_contract_fixture_source_paths() -> Vec<&'static str> { const PREDECESSOR_SUPERSEDED_SOURCE_PATHS: &[&str] = &[ "Cargo.toml", + "crates/core/src/currency.rs", + "crates/core/src/decimal.rs", + "crates/core/src/money.rs", + "crates/core/src/percent.rs", + "crates/core/src/quantity.rs", + "crates/core/src/quantity_price.rs", + "crates/core/src/unit.rs", "crates/event_store/src/error.rs", "crates/event_store/src/generated.rs", "crates/event_store/src/lib.rs", @@ -330,6 +365,11 @@ const PREDECESSOR_SUPERSEDED_SOURCE_PATHS: &[&str] = &[ "crates/event_store/src/store/protocol_reconciliation_v1.rs", ]; +#[cfg(test)] +pub(super) fn predecessor_superseded_source_paths() -> &'static [&'static str] { + PREDECESSOR_SUPERSEDED_SOURCE_PATHS +} + const GENERATED_ARTIFACT_PATHS: &[&str] = &[ MANIFEST_RELATIVE, MANIFEST_SCHEMA_RELATIVE,