commit 8667d09392bbfa1c12c6ba02a929aa56b5730a46
parent a0ae6c4617b4cd9a8bb8282a484b15639e9c89d6
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Jul 2026 17:46:17 +0000
core: quarantine superseded package surface
- retire legacy aliases and unchecked or upstream-facing value APIs
- enforce private invariant-preserving core values and checked serde
- migrate event, codec, trade, and sync consumers to checked accessors
- refresh conformance, public API, and architecture evidence
Diffstat:
42 files changed, 830 insertions(+), 2319 deletions(-)
diff --git a/crates/core/README.md b/crates/core/README.md
@@ -66,8 +66,9 @@ side effect.
Use `try_*`, `checked_*`, parsing, and exact-conversion APIs at trust
boundaries. They reject negative money or quantities, mismatched currencies or
units, division by zero, overflow, and lossy exact conversions as applicable.
-Compatibility constructors and operator overloads assume their inputs have
-already been validated and are not the boundary API for untrusted values.
+Public composite values keep invariant-bearing fields private and expose only
+checked construction and arithmetic, so invalid native state cannot be created
+through the supported public API.
Currency and unit parsers normalize their documented textual spellings.
Decimal display and serialization normalize insignificant trailing zeroes.
@@ -86,9 +87,9 @@ other number-limited formats do not introduce floating-point loss. Currencies
and units use their canonical string codes; aggregate values use named fields.
Wire compatibility is governed by the repository conformance vectors.
-Deserializing a composite value is not a substitute for boundary validation.
-Call the applicable validator or reconstruct through a checked constructor
-before committing untrusted decoded data.
+Deserializing a composite value re-applies the same invariants as its checked
+constructor. Callers remain responsible for application-specific policy and
+resource limits before committing untrusted decoded data.
## Execution and commit semantics
diff --git a/crates/core/src/currency.rs b/crates/core/src/currency.rs
@@ -144,9 +144,3 @@ impl<'de> Deserialize<'de> for Currency {
s.parse().map_err(D::Error::custom)
}
}
-
-#[deprecated(since = "0.1.0", note = "renamed to `Currency`")]
-pub use self::Currency as RadrootsCoreCurrency;
-
-#[deprecated(since = "0.1.0", note = "renamed to `currency::ParseError`")]
-pub use self::ParseError as RadrootsCoreCurrencyParseError;
diff --git a/crates/core/src/decimal.rs b/crates/core/src/decimal.rs
@@ -1,7 +1,6 @@
//! Fixed-precision decimal values, checked arithmetic, and exact conversions.
use core::fmt;
-use core::ops::{Add, Div, Mul, Sub};
use core::str::FromStr;
use rust_decimal::Decimal as RustDecimal;
use rust_decimal::prelude::ToPrimitive;
@@ -17,7 +16,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeErr
#[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
#[cfg_attr(all(test, feature = "std"), dto(as = "string"))]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
-pub struct Decimal(pub RustDecimal);
+pub struct Decimal(RustDecimal);
/// Errors produced while constructing or calculating with a [`Decimal`].
///
@@ -83,6 +82,16 @@ impl Decimal {
}
#[inline]
+ pub(crate) const fn from_backend(value: RustDecimal) -> Self {
+ Self(value)
+ }
+
+ #[inline]
+ pub(crate) const fn into_backend(self) -> RustDecimal {
+ self.0
+ }
+
+ #[inline]
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
@@ -123,8 +132,8 @@ impl Decimal {
Ok(())
}
#[inline]
- pub fn normalize(&self) -> RustDecimal {
- self.0.normalize()
+ pub fn normalize(&self) -> Self {
+ Self(self.0.normalize())
}
#[inline]
@@ -208,7 +217,7 @@ impl Decimal {
#[cfg(feature = "serde")]
impl Serialize for Decimal {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
- serializer.serialize_str(&self.normalize().to_string())
+ serializer.serialize_str(&self.0.normalize().to_string())
}
}
@@ -222,20 +231,10 @@ impl<'de> Deserialize<'de> for Decimal {
impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str(&self.normalize().to_string())
+ f.write_str(&self.0.normalize().to_string())
}
}
-impl From<RustDecimal> for Decimal {
- fn from(d: RustDecimal) -> Self {
- Self(canonicalize_zero(d))
- }
-}
-impl From<Decimal> for RustDecimal {
- fn from(d: Decimal) -> Self {
- d.0
- }
-}
impl From<u32> for Decimal {
fn from(v: u32) -> Self {
Self(RustDecimal::from(v))
@@ -257,31 +256,6 @@ impl From<i64> for Decimal {
}
}
-impl Add for Decimal {
- type Output = Self;
- fn add(self, rhs: Self) -> Self {
- Self(self.0 + rhs.0)
- }
-}
-impl Sub for Decimal {
- type Output = Self;
- fn sub(self, rhs: Self) -> Self {
- Self(self.0 - rhs.0)
- }
-}
-impl Mul for Decimal {
- type Output = Self;
- fn mul(self, rhs: Self) -> Self {
- Self(self.0 * rhs.0)
- }
-}
-impl Div for Decimal {
- type Output = Self;
- fn div(self, rhs: Self) -> Self {
- Self(self.0 / rhs.0)
- }
-}
-
impl FromStr for Decimal {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -291,6 +265,3 @@ impl FromStr for Decimal {
.map_err(normalize_parse_error)
}
}
-
-#[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
@@ -1,3 +1,5 @@
+use core::fmt;
+
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(feature = "std")]
@@ -39,15 +41,15 @@ pub enum DiscountValue {
Percent(crate::Percent),
}
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
#[cfg_attr(all(test, feature = "std"), dto(export))]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub struct Discount {
- pub scope: DiscountScope,
- pub threshold: DiscountThreshold,
- pub value: DiscountValue,
+ scope: DiscountScope,
+ threshold: DiscountThreshold,
+ value: DiscountValue,
}
#[non_exhaustive]
@@ -109,7 +111,7 @@ impl Discount {
DiscountValue::MoneyPerBin(money) => money
.ensure_non_negative()
.map_err(|_| Error::NegativeValue)?,
- DiscountValue::Percent(percent) if percent.value.is_sign_negative() => {
+ DiscountValue::Percent(percent) if percent.value().is_sign_negative() => {
return Err(Error::NegativeValue);
}
DiscountValue::Percent(_) => {}
@@ -122,15 +124,17 @@ impl Discount {
}
}
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::Discount`")]
-pub use self::Discount as RadrootsCoreDiscount;
-
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::DiscountScope`")]
-pub use self::DiscountScope as RadrootsCoreDiscountScope;
-
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::DiscountThreshold`")]
-pub use self::DiscountThreshold as RadrootsCoreDiscountThreshold;
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Discount {
+ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ #[derive(serde::Deserialize)]
+ struct Wire {
+ scope: DiscountScope,
+ threshold: DiscountThreshold,
+ value: DiscountValue,
+ }
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::DiscountValue`")]
-pub use self::DiscountValue as RadrootsCoreDiscountValue;
-use core::fmt;
+ let wire = Wire::deserialize(deserializer)?;
+ Self::try_new(wire.scope, wire.threshold, wire.value).map_err(serde::de::Error::custom)
+ }
+}
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
@@ -29,36 +29,3 @@ pub use percent::Percent;
pub use pricing::QuantityPrice;
pub use quantity::Quantity;
pub use unit::Unit;
-
-#[allow(deprecated)]
-pub use currency::RadrootsCoreCurrencyParseError;
-#[allow(deprecated)]
-pub use discount::{
- RadrootsCoreDiscount, RadrootsCoreDiscountScope, RadrootsCoreDiscountThreshold,
- RadrootsCoreDiscountValue,
-};
-#[allow(deprecated)]
-pub use money::RadrootsCoreMoneyInvariantError;
-#[allow(deprecated)]
-pub use percent::RadrootsCorePercentParseError;
-#[allow(deprecated)]
-pub use quantity::RadrootsCoreQuantityInvariantError;
-#[allow(deprecated)]
-pub use quantity_price::{RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps};
-#[allow(deprecated)]
-pub use unit::{RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError};
-
-#[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
@@ -1,17 +1,16 @@
//! Non-negative monetary values and currency-aware quantization.
use core::fmt;
-use rust_decimal::Decimal;
-use rust_decimal::RoundingStrategy;
+use rust_decimal::Decimal as BackendDecimal;
use rust_decimal::prelude::ToPrimitive;
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
#[cfg_attr(all(test, feature = "std"), dto(export))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Money {
- pub amount: crate::Decimal,
- pub currency: crate::Currency,
+ amount: crate::Decimal,
+ currency: crate::Currency,
}
#[non_exhaustive]
@@ -57,15 +56,6 @@ impl From<crate::decimal::Error> for Error {
}
impl Money {
- /// Compatibility constructor for already-validated internal values.
- ///
- /// New boundary code should use [`Self::try_new`]. The unchecked shape is
- /// retained only until the scheduled first-party consumer migration.
- #[inline]
- pub fn new(amount: crate::Decimal, currency: crate::Currency) -> Self {
- Self { amount, currency }
- }
-
#[inline]
pub fn try_new(amount: crate::Decimal, currency: crate::Currency) -> Result<Self, Error> {
if amount.is_sign_negative() && !amount.is_zero() {
@@ -105,7 +95,7 @@ impl Money {
}
#[inline]
- pub fn ensure_non_negative(&self) -> Result<(), Error> {
+ pub(crate) fn ensure_non_negative(&self) -> Result<(), Error> {
if self.amount.is_sign_negative() && !self.amount.is_zero() {
return Err(Error::NegativeAmount);
}
@@ -114,23 +104,15 @@ impl Money {
#[inline]
pub fn quantize_to_currency(self) -> Self {
- self.quantize_to_currency_with_strategy(RoundingStrategy::MidpointAwayFromZero)
- }
-
- /// Rounds to the currency's minor-unit exponent with the supplied
- /// strategy. [`Self::quantize_to_currency`] uses
- /// [`RoundingStrategy::MidpointAwayFromZero`].
- #[inline]
- pub fn quantize_to_currency_with_strategy(mut self, strategy: RoundingStrategy) -> Self {
- let e = self.currency.minor_unit_exponent();
- self.amount.0 = self.amount.0.round_dp_with_strategy(e, strategy);
- self
- }
-
- #[inline]
- pub fn with_scale(mut self, scale: u32) -> Self {
- self.amount.rescale(scale);
- self
+ let mut value = self;
+ let e = value.currency.minor_unit_exponent();
+ value.amount = crate::Decimal::from_backend(
+ value
+ .amount
+ .into_backend()
+ .round_dp_with_strategy(e, rust_decimal::RoundingStrategy::MidpointAwayFromZero),
+ );
+ value
}
/// Changes the amount scale without rounding or changing its value.
@@ -142,8 +124,6 @@ impl Money {
#[inline]
pub fn checked_add(&self, rhs: &Self) -> Result<Self, Error> {
- self.ensure_non_negative()?;
- rhs.ensure_non_negative()?;
if self.currency != rhs.currency {
return Err(Error::CurrencyMismatch);
}
@@ -152,8 +132,6 @@ impl Money {
#[inline]
pub fn checked_sub(&self, rhs: &Self) -> Result<Self, Error> {
- self.ensure_non_negative()?;
- rhs.ensure_non_negative()?;
if self.currency != rhs.currency {
return Err(Error::CurrencyMismatch);
}
@@ -162,31 +140,22 @@ impl Money {
#[inline]
pub fn checked_mul_decimal(&self, factor: crate::Decimal) -> Result<Self, Error> {
- self.ensure_non_negative()?;
Self::try_new(self.amount.checked_mul(factor)?, self.currency)
}
#[inline]
pub fn checked_div_decimal(&self, divisor: crate::Decimal) -> Result<Self, Error> {
- self.ensure_non_negative()?;
Self::try_new(self.amount.checked_div(divisor)?, self.currency)
}
#[inline]
- pub fn mul_decimal(&self, factor: crate::Decimal) -> Self {
- Self::new(self.amount * factor, self.currency)
- }
-
- #[inline]
- 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::Currency) -> Self {
let e = currency.minor_unit_exponent();
- let major = Decimal::from_i128_with_scale(amount_minor as i128, e);
- Self::new(crate::Decimal(major), currency)
+ let major = BackendDecimal::from_i128_with_scale(amount_minor as i128, e);
+ Self {
+ amount: crate::Decimal::from_backend(major),
+ currency,
+ }
}
#[inline]
@@ -195,28 +164,27 @@ impl Money {
}
#[inline]
- fn pow10(e: u32) -> Decimal {
+ fn pow10(e: u32) -> BackendDecimal {
match e {
- 0 => Decimal::ONE,
- 1 => Decimal::from(10u32),
- 2 => Decimal::from(100u32),
- 3 => Decimal::from(1_000u32),
+ 0 => BackendDecimal::ONE,
+ 1 => BackendDecimal::from(10u32),
+ 2 => BackendDecimal::from(100u32),
+ 3 => BackendDecimal::from(1_000u32),
_ => {
let p = 10u128.pow(e.min(38));
- Decimal::from(p)
+ BackendDecimal::from(p)
}
}
}
#[inline]
pub fn to_minor_units_u64_exact(&self) -> Result<u64, Error> {
- self.ensure_non_negative()?;
let e = self.currency.minor_unit_exponent();
let as_minor = self
.amount
- .checked_mul(crate::Decimal(Self::pow10(e)))
+ .checked_mul(crate::Decimal::from_backend(Self::pow10(e)))
.map_err(|_| Error::AmountOverflow)?
- .0;
+ .into_backend();
if !as_minor.fract().is_zero() {
return Err(Error::NotWholeMinorUnits);
@@ -225,10 +193,12 @@ impl Money {
}
#[inline]
- pub fn to_minor_units_u64_rounded(&self, strategy: RoundingStrategy) -> Result<u64, Error> {
- self.ensure_non_negative()?;
+ pub fn to_minor_units_u64_rounded(&self) -> Result<u64, Error> {
let e = self.currency.minor_unit_exponent();
- let scaled = self.amount.0.round_dp_with_strategy(e, strategy);
+ let scaled = self
+ .amount
+ .into_backend()
+ .round_dp_with_strategy(e, rust_decimal::RoundingStrategy::MidpointAwayFromZero);
let as_minor = scaled
.checked_mul(Self::pow10(e))
.ok_or(Error::AmountOverflow)?;
@@ -243,50 +213,42 @@ impl Money {
}
#[inline]
- pub fn to_minor_units_u32_rounded(&self, strategy: RoundingStrategy) -> Result<u32, Error> {
- let v = self.to_minor_units_u64_rounded(strategy)?;
+ pub fn to_minor_units_u32_rounded(&self) -> Result<u32, Error> {
+ let v = self.to_minor_units_u64_rounded()?;
u32::try_from(v).map_err(|_| Error::AmountOverflow)
}
}
-impl fmt::Display for Money {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} {}", self.amount, self.currency)
- }
-}
-
-use core::ops::{Div, Mul};
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Money {
+ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ #[derive(serde::Deserialize)]
+ struct Wire {
+ amount: crate::Decimal,
+ currency: crate::Currency,
+ }
-impl Mul<crate::Decimal> for Money {
- type Output = Self;
- fn mul(self, rhs: crate::Decimal) -> Self {
- self.mul_decimal(rhs)
+ let wire = Wire::deserialize(deserializer)?;
+ Self::try_new(wire.amount, wire.currency).map_err(serde::de::Error::custom)
}
}
-impl Div<crate::Decimal> for Money {
- type Output = Self;
- fn div(self, rhs: crate::Decimal) -> Self {
- self.div_decimal(rhs)
+impl fmt::Display for Money {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} {}", self.amount, self.currency)
}
}
-#[deprecated(since = "0.1.0", note = "renamed to `Money`")]
-pub use self::Money as RadrootsCoreMoney;
-
-#[deprecated(since = "0.1.0", note = "renamed to `money::Error`")]
-pub use self::Error as RadrootsCoreMoneyInvariantError;
-
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pow10_internal_paths_cover_fallback_branches() {
- 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));
+ assert_eq!(Money::pow10(0), BackendDecimal::ONE);
+ assert_eq!(Money::pow10(1), BackendDecimal::from(10u32));
+ assert_eq!(Money::pow10(2), BackendDecimal::from(100u32));
+ assert_eq!(Money::pow10(3), BackendDecimal::from(1_000u32));
+ assert_eq!(Money::pow10(6), BackendDecimal::from(1_000_000u32));
}
}
diff --git a/crates/core/src/percent.rs b/crates/core/src/percent.rs
@@ -13,7 +13,7 @@ use crate::money::Money;
pub struct Percent {
#[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))]
#[cfg_attr(all(test, feature = "std"), dto(as = "string"))]
- pub value: Decimal,
+ value: Decimal,
}
impl Percent {
@@ -28,10 +28,8 @@ impl Percent {
}
#[inline]
- pub fn from_ratio(ratio_0_to_1: Decimal) -> Self {
- Self {
- value: ratio_0_to_1 * Decimal::from(100u32),
- }
+ pub const fn value(&self) -> Decimal {
+ self.value
}
#[inline]
@@ -42,26 +40,11 @@ impl Percent {
}
#[inline]
- pub fn to_ratio(&self) -> Decimal {
- self.value / Decimal::from(100u32)
- }
-
- #[inline]
pub fn try_to_ratio(&self) -> Result<Decimal, crate::decimal::Error> {
self.value.checked_div(Decimal::from(100u32))
}
#[inline]
- pub fn of_money(&self, base: &Money) -> Money {
- base.mul_decimal(self.to_ratio())
- }
-
- #[inline]
- pub fn of_money_quantized(&self, base: &Money) -> Money {
- base.mul_decimal(self.to_ratio()).quantize_to_currency()
- }
-
- #[inline]
pub fn try_of_money(&self, base: &Money) -> Result<Money, crate::money::Error> {
base.checked_mul_decimal(self.try_to_ratio().map_err(crate::money::Error::from)?)
}
@@ -107,9 +90,3 @@ impl fmt::Display for ParseError {
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
-
-#[deprecated(since = "0.1.0", note = "renamed to `Percent`")]
-pub use self::Percent as RadrootsCorePercent;
-
-#[deprecated(since = "0.1.0", note = "renamed to `percent::ParseError`")]
-pub use self::ParseError as RadrootsCorePercentParseError;
diff --git a/crates/core/src/quantity.rs b/crates/core/src/quantity.rs
@@ -10,33 +10,20 @@ use alloc::string::String;
#[cfg(feature = "std")]
use std::string::String;
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
#[cfg_attr(all(test, feature = "std"), dto(export))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Quantity {
#[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))]
#[cfg_attr(all(test, feature = "std"), dto(as = "string"))]
- pub amount: Decimal,
- pub unit: Unit,
+ amount: Decimal,
+ unit: Unit,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
- pub label: Option<String>,
+ label: Option<String>,
}
impl Quantity {
- /// Compatibility constructor for already-validated internal values.
- ///
- /// New boundary code should use [`Self::try_new`]. The unchecked shape is
- /// retained only until the scheduled first-party consumer migration.
- #[inline]
- pub fn new(amount: Decimal, unit: Unit) -> Self {
- Self {
- amount,
- unit,
- label: None,
- }
- }
-
#[inline]
pub fn try_new(amount: Decimal, unit: Unit) -> Result<Self, Error> {
if amount.is_sign_negative() && !amount.is_zero() {
@@ -129,7 +116,7 @@ impl Quantity {
}
#[inline]
- pub fn ensure_non_negative(&self) -> Result<(), Error> {
+ pub(crate) fn ensure_non_negative(&self) -> Result<(), Error> {
if self.amount.is_sign_negative() && !self.amount.is_zero() {
return Err(Error::NegativeAmount);
}
@@ -137,15 +124,7 @@ impl Quantity {
}
#[inline]
- pub fn with_scale(mut self, scale: u32) -> Self {
- self.amount.rescale(scale);
- self
- }
-
- #[inline]
pub fn try_add(&self, rhs: &Quantity) -> Result<Quantity, Error> {
- self.ensure_non_negative()?;
- rhs.ensure_non_negative()?;
if self.unit != rhs.unit {
return Err(Error::UnitMismatch);
}
@@ -158,8 +137,6 @@ impl Quantity {
#[inline]
pub fn try_sub(&self, rhs: &Quantity) -> Result<Quantity, Error> {
- self.ensure_non_negative()?;
- rhs.ensure_non_negative()?;
if self.unit != rhs.unit {
return Err(Error::UnitMismatch);
}
@@ -184,7 +161,6 @@ impl Quantity {
#[inline]
pub fn checked_mul_decimal(&self, factor: Decimal) -> Result<Quantity, Error> {
- self.ensure_non_negative()?;
let amount = self.amount.checked_mul(factor)?;
if amount.is_sign_negative() {
return Err(Error::NegativeAmount);
@@ -198,7 +174,6 @@ impl Quantity {
#[inline]
pub fn checked_div_decimal(&self, divisor: Decimal) -> Result<Quantity, Error> {
- self.ensure_non_negative()?;
let amount = self.amount.checked_div(divisor)?;
if amount.is_sign_negative() {
return Err(Error::NegativeAmount);
@@ -209,23 +184,24 @@ impl Quantity {
label: self.label.clone(),
})
}
+}
- #[inline]
- pub fn mul_decimal(&self, factor: Decimal) -> Quantity {
- Quantity {
- amount: self.amount * factor,
- unit: self.unit,
- label: self.label.clone(),
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Quantity {
+ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ #[derive(serde::Deserialize)]
+ struct Wire {
+ #[serde(with = "crate::serde_ext::decimal_str")]
+ amount: Decimal,
+ unit: Unit,
+ #[serde(default)]
+ label: Option<String>,
}
- }
- #[inline]
- pub fn div_decimal(&self, divisor: Decimal) -> Quantity {
- Quantity {
- amount: self.amount / divisor,
- unit: self.unit,
- label: self.label.clone(),
- }
+ let wire = Wire::deserialize(deserializer)?;
+ Self::try_new(wire.amount, wire.unit)
+ .map(|quantity| quantity.with_optional_label(wire.label))
+ .map_err(serde::de::Error::custom)
}
}
@@ -274,33 +250,3 @@ impl From<crate::decimal::Error> for Error {
}
}
}
-
-use core::ops::{Div, Mul};
-
-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,
- }
- }
-}
-
-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;
-
-#[deprecated(since = "0.1.0", note = "renamed to `quantity::Error`")]
-pub use self::Error as RadrootsCoreQuantityInvariantError;
diff --git a/crates/core/src/quantity_price.rs b/crates/core/src/quantity_price.rs
@@ -2,13 +2,13 @@ use core::fmt;
use crate::{Decimal, Money, Quantity, Unit};
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
#[cfg_attr(all(test, feature = "std"), dto(export))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct QuantityPrice {
- pub amount: Money,
- pub quantity: Quantity,
+ amount: Money,
+ quantity: Quantity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -60,10 +60,6 @@ mod sealed {
/// Sealed pricing operations implemented by [`QuantityPrice`].
///
-/// The `try_*` methods are the canonical checked surface. The infallible
-/// methods remain temporarily for first-party source compatibility and return
-/// zero when the corresponding checked operation fails.
-///
/// Downstream crates may call this trait but cannot implement it:
///
/// ```compile_fail
@@ -73,51 +69,30 @@ mod sealed {
/// struct ForeignPrice;
///
/// impl QuantityPriceOps for ForeignPrice {
-/// fn cost_for(&self, _: &Quantity) -> Money { panic!() }
-/// fn cost_for_rounded(&self, _: &Quantity) -> Money { panic!() }
-/// fn cost_for_with_quantized_price(&self, _: &Quantity) -> Money { panic!() }
/// fn try_cost_for(&self, _: &Quantity) -> Result<Money, Error> { panic!() }
/// fn try_cost_for_rounded(&self, _: &Quantity) -> Result<Money, Error> { panic!() }
/// }
/// ```
pub trait QuantityPriceOps: sealed::Sealed {
- /// Compatibility operation that returns zero on invalid input.
- #[must_use]
- fn cost_for(&self, qty: &Quantity) -> Money;
-
- /// Calculates first, then rounds the final result to the currency exponent
- /// with midpoint-away-from-zero rounding.
- #[must_use]
- fn cost_for_rounded(&self, qty: &Quantity) -> Money;
-
- /// Rounds the price first, then calculates the requested cost without a
- /// second quantization.
- #[must_use]
- fn cost_for_with_quantized_price(&self, qty: &Quantity) -> Money;
-
+ /// Calculates the requested cost without silently converting invalid
+ /// quantities, unit mismatches, or arithmetic failures into zero.
fn try_cost_for(&self, qty: &Quantity) -> Result<Money, Error>;
+ /// Calculates first, then rounds the final result to the currency exponent
+ /// with deterministic midpoint-away-from-zero rounding.
fn try_cost_for_rounded(&self, qty: &Quantity) -> Result<Money, Error>;
}
impl QuantityPrice {
- /// Compatibility constructor for already-validated internal values.
- ///
- /// New boundary code should use [`Self::try_new`].
- #[inline]
- pub fn new(amount: Money, quantity: Quantity) -> Self {
- Self { amount, quantity }
- }
-
#[inline]
pub fn try_new(amount: Money, quantity: Quantity) -> Result<Self, Error> {
amount
.ensure_non_negative()
.map_err(|_| Error::NegativePrice)?;
- if quantity.amount.is_sign_negative() {
+ if quantity.amount().is_sign_negative() {
return Err(Error::PerQuantityNegative);
}
- if quantity.amount.is_zero() {
+ if quantity.amount().is_zero() {
return Err(Error::PerQuantityZero);
}
Ok(Self { amount, quantity })
@@ -142,7 +117,7 @@ impl QuantityPrice {
pub fn try_cost_for_amount_in(&self, amount: Decimal, unit: Unit) -> Result<Money, Error> {
use crate::unit::convert_unit_decimal;
- let target = self.quantity.unit;
+ let target = self.quantity.unit();
let normalized = if unit == target {
amount
@@ -158,13 +133,13 @@ impl QuantityPrice {
#[inline]
pub fn try_cost_for_quantity_in(&self, qty: &Quantity) -> Result<Money, Error> {
- self.try_cost_for_amount_in(qty.amount, qty.unit)
+ 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 == Decimal::ONE
+ self.quantity.unit() == self.quantity.unit().canonical_unit()
+ && self.quantity.amount() == Decimal::ONE
}
#[inline]
@@ -173,11 +148,11 @@ impl QuantityPrice {
self.validate()?;
- let normalized = if self.quantity.unit == unit {
- self.quantity.amount
+ let normalized = if self.quantity.unit() == unit {
+ self.quantity.amount()
} else {
- convert_unit_decimal(self.quantity.amount, self.quantity.unit, unit)
- .map_err(|error| map_conversion_error(error, self.quantity.unit, unit))?
+ convert_unit_decimal(self.quantity.amount(), self.quantity.unit(), unit)
+ .map_err(|error| map_conversion_error(error, self.quantity.unit(), unit))?
};
if normalized.is_zero() {
@@ -188,66 +163,46 @@ impl QuantityPrice {
.amount
.checked_div_decimal(normalized)
.map_err(|_| Error::ArithmeticOverflow)?;
- Self::try_new(amount, Quantity::new(Decimal::ONE, unit))
+ let quantity =
+ Quantity::try_new(Decimal::ONE, unit).map_err(|_| Error::ArithmeticOverflow)?;
+ Self::try_new(amount, quantity)
}
#[inline]
pub fn try_to_canonical_unit_price(&self) -> Result<QuantityPrice, Error> {
- self.try_to_unit_price(self.quantity.unit.canonical_unit())
+ self.try_to_unit_price(self.quantity.unit().canonical_unit())
}
}
-impl QuantityPriceOps for QuantityPrice {
- #[inline]
- fn cost_for(&self, qty: &Quantity) -> Money {
- if qty.amount.is_zero() {
- return Money::zero(self.amount.currency);
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QuantityPrice {
+ fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ #[derive(serde::Deserialize)]
+ struct Wire {
+ amount: Money,
+ quantity: Quantity,
}
- if self.quantity.amount.is_zero() {
- return Money::zero(self.amount.currency);
- }
- if qty.unit != self.quantity.unit {
- return Money::zero(self.amount.currency);
- }
-
- let ratio = qty.amount / self.quantity.amount;
- self.amount.mul_decimal(ratio)
- }
-
- #[inline]
- fn cost_for_rounded(&self, qty: &Quantity) -> Money {
- self.cost_for(qty).quantize_to_currency()
- }
- #[inline]
- fn cost_for_with_quantized_price(&self, qty: &Quantity) -> Money {
- if qty.amount.is_zero() {
- return Money::zero(self.amount.currency);
- }
- if self.quantity.amount.is_zero() {
- return Money::zero(self.amount.currency);
- }
- if qty.unit != self.quantity.unit {
- 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)
+ let wire = Wire::deserialize(deserializer)?;
+ Self::try_new(wire.amount, wire.quantity).map_err(serde::de::Error::custom)
}
+}
+impl QuantityPriceOps for QuantityPrice {
#[inline]
fn try_cost_for(&self, qty: &Quantity) -> Result<Money, Error> {
self.validate()?;
qty.ensure_non_negative()
.map_err(|_| Error::NegativeRequestedQuantity)?;
- if qty.unit != self.quantity.unit {
+ if qty.unit() != self.quantity.unit() {
return Err(Error::UnitMismatch {
- have: qty.unit,
- want: self.quantity.unit,
+ have: qty.unit(),
+ want: self.quantity.unit(),
});
}
let ratio = qty
- .amount
- .checked_div(self.quantity.amount)
+ .amount()
+ .checked_div(self.quantity.amount())
.map_err(|_| Error::ArithmeticOverflow)?;
self.amount
.checked_mul_decimal(ratio)
@@ -259,12 +214,3 @@ impl QuantityPriceOps for QuantityPrice {
Ok(self.try_cost_for(qty)?.quantize_to_currency())
}
}
-
-#[deprecated(since = "0.1.0", note = "renamed to `QuantityPrice`")]
-pub use self::QuantityPrice as RadrootsCoreQuantityPrice;
-
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::Error`")]
-pub use self::Error as RadrootsCoreQuantityPriceError;
-
-#[deprecated(since = "0.1.0", note = "renamed to `pricing::QuantityPriceOps`")]
-pub use self::QuantityPriceOps as RadrootsCoreQuantityPriceOps;
diff --git a/crates/core/src/unit.rs b/crates/core/src/unit.rs
@@ -200,18 +200,6 @@ impl<'de> Deserialize<'de> for Unit {
}
}
-#[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;
-
-#[deprecated(since = "0.1.0", note = "renamed to `unit::ParseError`")]
-pub use self::ParseError as RadrootsCoreUnitParseError;
-
-#[deprecated(since = "0.1.0", note = "renamed to `unit::ConvertError`")]
-pub use self::ConvertError as RadrootsCoreUnitConvertError;
-
#[inline]
pub fn parse_mass_unit(s: &str) -> Result<Unit, ParseError> {
let u: Unit = Unit::from_str(s)?;
diff --git a/crates/core/tests/common/mod.rs b/crates/core/tests/common/mod.rs
@@ -13,11 +13,11 @@ pub fn currency(code: &str) -> Currency {
}
pub fn money(amount: &str, code: &str) -> Money {
- Money::new(dec(amount), currency(code))
+ Money::try_new(dec(amount), currency(code)).expect("valid money")
}
pub fn qty(amount: &str, unit: Unit) -> Quantity {
- Quantity::new(dec(amount), unit)
+ Quantity::try_new(dec(amount), unit).expect("valid quantity")
}
pub fn percent(s: &str) -> Percent {
diff --git a/crates/core/tests/decimal.rs b/crates/core/tests/decimal.rs
@@ -105,15 +105,10 @@ fn float_conversion_rejects_non_finite_values() {
}
#[test]
-fn from_str_exact_and_conversion_impl_paths_are_exercised() {
+fn from_str_exact_and_primitive_conversion_paths_are_exercised() {
let exact = Decimal::from_str_exact("42.000").unwrap();
assert_eq!(exact, common::dec("42"));
- 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 = Decimal::from(7u32);
let from_i32 = Decimal::from(-2i32);
let from_u64 = Decimal::from(11u64);
diff --git a/crates/core/tests/discount.rs b/crates/core/tests/discount.rs
@@ -1,74 +1,12 @@
mod common;
use radroots_core::{
- Percent, Unit,
+ Percent,
pricing::{Discount, DiscountError, DiscountScope, DiscountThreshold, DiscountValue},
};
#[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", 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 = Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
- bin_id: "bin-1".to_string(),
- min: 2,
- },
- value: DiscountValue::MoneyPerBin(money_pos.clone()),
- };
- assert!(d.is_non_negative());
-
- let d = Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
- bin_id: "bin-1".to_string(),
- min: 2,
- },
- value: DiscountValue::MoneyPerBin(money_neg),
- };
- assert!(!d.is_non_negative());
-
- let d = Discount {
- scope: DiscountScope::OrderTotal,
- threshold: DiscountThreshold::OrderQuantity {
- min: qty_pos.clone(),
- },
- value: DiscountValue::Percent(pct_pos.clone()),
- };
- assert!(d.is_non_negative());
-
- let d = Discount {
- scope: DiscountScope::OrderTotal,
- threshold: DiscountThreshold::OrderQuantity { min: qty_neg },
- value: DiscountValue::Percent(pct_pos),
- };
- assert!(!d.is_non_negative());
-
- let d = Discount {
- scope: DiscountScope::OrderTotal,
- threshold: DiscountThreshold::OrderQuantity { min: qty_pos },
- value: DiscountValue::Percent(pct_neg),
- };
- assert!(!d.is_non_negative());
-}
-
-#[test]
-fn checked_constructor_rejects_negative_thresholds_and_values() {
- let negative_threshold = Discount::try_new(
- DiscountScope::OrderTotal,
- DiscountThreshold::OrderQuantity {
- min: common::qty("-1", Unit::Each),
- },
- DiscountValue::Percent(Percent::new(common::dec("10"))),
- );
- assert_eq!(negative_threshold, Err(DiscountError::NegativeThreshold));
-
+fn checked_constructor_rejects_negative_percent_values() {
let negative_value = Discount::try_new(
DiscountScope::Bin,
DiscountThreshold::BinCount {
@@ -98,6 +36,11 @@ fn checked_constructor_and_accessors_preserve_valid_shape() {
));
assert!(matches!(discount.value(), DiscountValue::MoneyPerBin(_)));
assert_eq!(discount.validate(), Ok(()));
+ assert!(discount.is_non_negative());
+}
+
+#[test]
+fn discount_error_messages_are_stable() {
assert_eq!(
DiscountError::NegativeThreshold.to_string(),
"discount threshold must be non-negative"
diff --git a/crates/core/tests/money.rs b/crates/core/tests/money.rs
@@ -1,239 +1,112 @@
mod common;
use radroots_core::{Currency, Decimal, Money, money::Error};
-use rust_decimal::RoundingStrategy;
#[test]
-fn zero_and_is_zero() {
- let usd = Currency::USD;
- let zero = Money::zero(usd);
+fn zero_and_checked_constructor_preserve_invariants() {
+ let zero = Money::zero(Currency::USD);
assert!(zero.is_zero());
- assert_eq!(zero.currency, usd);
-}
-
-#[test]
-fn ensure_non_negative_rejects_negative_amount() {
- let money = Money::new(common::dec("-1"), Currency::USD);
- assert_eq!(money.ensure_non_negative(), Err(Error::NegativeAmount));
-}
-
-#[test]
-fn ensure_non_negative_accepts_zero_and_positive() {
- 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(()));
-}
+ assert_eq!(zero.amount(), Decimal::ZERO);
+ assert_eq!(zero.currency(), Currency::USD);
-#[test]
-fn checked_constructor_rejects_negative_and_canonicalizes_zero() {
assert_eq!(
Money::try_new(common::dec("-0.01"), Currency::USD),
Err(Error::NegativeAmount)
);
- let zero = Money::try_new(common::dec("-0.00"), Currency::USD).unwrap();
- assert_eq!(zero.amount(), Decimal::ZERO);
- assert_eq!(zero.currency(), Currency::USD);
-}
-
-#[test]
-fn quantize_to_currency_rounds_midpoint_away_from_zero() {
- 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"));
- assert_eq!(c.amount, common::dec("-1.24"));
+ let signed_zero = Money::try_new(common::dec("-0.00"), Currency::USD).unwrap();
+ assert_eq!(signed_zero.amount(), Decimal::ZERO);
}
#[test]
-fn quantize_to_currency_with_strategy_uses_strategy() {
- let usd = Currency::USD;
- let a = Money::new(common::dec("1.235"), usd)
- .quantize_to_currency_with_strategy(RoundingStrategy::MidpointTowardZero);
- 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"));
+fn quantization_is_deterministic_midpoint_away_from_zero() {
+ let a = common::money("1.234", "USD").quantize_to_currency();
+ let b = common::money("1.235", "USD").quantize_to_currency();
+ assert_eq!(a.amount(), common::dec("1.23"));
+ assert_eq!(b.amount(), common::dec("1.24"));
}
#[test]
-fn checked_add_and_sub_require_currency_match() {
- 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!(a.checked_add(&c), Err(Error::CurrencyMismatch));
- assert_eq!(b.checked_sub(&a).unwrap().amount, common::dec("1.00"));
-}
+fn checked_arithmetic_reports_domain_failures() {
+ let one = common::money("1.00", "USD");
+ let two = common::money("2.00", "USD");
+ let euros = common::money("3.00", "EUR");
-#[test]
-fn checked_sub_mismatch_returns_currency_error() {
- 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(Error::CurrencyMismatch));
-}
-
-#[test]
-fn checked_arithmetic_rejects_invalid_results() {
- let usd = Currency::USD;
- let one = Money::try_new(Decimal::ONE, usd).unwrap();
- let two = Money::try_new(Decimal::from(2u32), usd).unwrap();
+ assert_eq!(one.checked_add(&two).unwrap().amount(), common::dec("3"));
+ assert_eq!(one.checked_add(&euros), Err(Error::CurrencyMismatch));
+ assert_eq!(two.checked_sub(&one).unwrap().amount(), common::dec("1"));
assert_eq!(one.checked_sub(&two), Err(Error::NegativeAmount));
-
- let max = Money::try_new(Decimal::MAX, usd).unwrap();
- assert_eq!(max.checked_add(&one), Err(Error::ArithmeticOverflow));
- assert_eq!(
- one.checked_div_decimal(Decimal::ZERO),
- Err(Error::DivisionByZero)
- );
assert_eq!(
one.checked_mul_decimal(common::dec("-1")),
Err(Error::NegativeAmount)
);
-}
-
-#[test]
-fn minor_units_exact_and_rounded() {
- 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!(
- frac.to_minor_units_u64_exact(),
- Err(Error::NotWholeMinorUnits)
- );
- assert_eq!(
- rounded
- .to_minor_units_u64_rounded(RoundingStrategy::MidpointAwayFromZero)
- .unwrap(),
- 124
- );
-}
-
-#[test]
-fn minor_units_cover_additional_currency_exponents() {
- 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)
- .unwrap(),
- 123
+ one.checked_div_decimal(Decimal::ZERO),
+ Err(Error::DivisionByZero)
);
- 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 = 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(Error::AmountOverflow)
- );
+ let max = Money::try_new(Decimal::MAX, Currency::USD).unwrap();
+ assert_eq!(max.checked_add(&one), Err(Error::ArithmeticOverflow));
}
#[test]
-fn minor_units_u32_exact_success_path_is_exercised() {
- 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 = Money::new(common::dec("1.001"), usd);
+fn minor_units_exact_and_rounded_cover_currency_exponents() {
+ let exact = common::money("1.23", "USD");
+ let fractional = common::money("1.234", "USD");
+ let rounded = common::money("1.235", "USD");
+ assert_eq!(exact.to_minor_units_u64_exact(), Ok(123));
assert_eq!(
- fractional.to_minor_units_u32_exact(),
+ fractional.to_minor_units_u64_exact(),
Err(Error::NotWholeMinorUnits)
);
-}
-
-#[test]
-fn from_minor_units_u32_and_u32_rounded_paths_are_exercised() {
- 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
- .to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero)
- .unwrap(),
- 505
- );
-}
+ assert_eq!(rounded.to_minor_units_u64_rounded(), Ok(124));
-#[test]
-fn minor_units_u32_rounded_overflow_is_detected() {
- 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(Error::AmountOverflow)
+ common::money("123", "JPY").to_minor_units_u64_exact(),
+ Ok(123)
);
- let negative = Money::new(common::dec("-1.00"), usd);
assert_eq!(
- negative.to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero),
- Err(Error::NegativeAmount)
+ common::money("1.234", "KWD").to_minor_units_u64_exact(),
+ Ok(1234)
);
}
#[test]
-fn with_scale_path_is_exercised() {
- 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 = Currency::USD;
- let money = Money::from_minor_units_u64(12345, usd);
- assert_eq!(money.to_minor_units_u64_exact().unwrap(), 12345);
-}
-
-#[test]
-fn minor_units_roundtrip_across_supported_exponents() {
+fn minor_unit_constructors_roundtrip_and_detect_u32_overflow() {
for currency in [Currency::JPY, Currency::USD, common::currency("KWD")] {
for minor in [0, 1, 99, 12_345, u32::MAX as u64] {
let money = Money::from_minor_units_u64(minor, currency);
- assert_eq!(money.to_minor_units_u64_exact().unwrap(), minor);
+ assert_eq!(money.to_minor_units_u64_exact(), Ok(minor));
}
}
+
+ let from_u32 = Money::from_minor_units_u32(505, Currency::USD);
+ assert_eq!(from_u32.amount(), common::dec("5.05"));
+ assert_eq!(from_u32.to_minor_units_u32_rounded(), Ok(505));
+
+ let too_large = Money::from_minor_units_u64(u64::from(u32::MAX) + 1, Currency::USD);
+ assert_eq!(
+ too_large.to_minor_units_u32_exact(),
+ Err(Error::AmountOverflow)
+ );
+ assert_eq!(
+ too_large.to_minor_units_u32_rounded(),
+ Err(Error::AmountOverflow)
+ );
}
#[test]
fn exact_scale_change_never_rounds() {
- let money = Money::try_new(common::dec("1.2300"), Currency::USD).unwrap();
+ let money = common::money("1.2300", "USD");
assert_eq!(money.try_with_scale_exact(2).unwrap().amount().scale(), 2);
assert_eq!(
- Money::try_new(common::dec("1.235"), Currency::USD)
- .unwrap()
- .try_with_scale_exact(2),
+ common::money("1.235", "USD").try_with_scale_exact(2),
Err(Error::PrecisionLoss)
);
}
#[test]
-fn display_and_operator_impl_paths_are_exercised() {
- 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");
- assert_eq!(times.amount, common::dec("20"));
- let divided = m / common::dec("4");
- assert_eq!(divided.amount, common::dec("2.5"));
-}
-
-#[test]
-fn invariant_error_display_variants_are_exercised() {
+fn display_and_error_messages_are_stable() {
+ assert_eq!(common::money("10", "USD").to_string(), "10 USD");
assert_eq!(
Error::NegativeAmount.to_string(),
"money amount must be ≥ 0"
diff --git a/crates/core/tests/package_boundary.rs b/crates/core/tests/package_boundary.rs
@@ -24,25 +24,10 @@ fn canonical_value_paths_are_public() {
let _ = core::mem::size_of::<radroots_core::pricing::Error>();
}
-#[allow(deprecated)]
#[test]
-fn temporary_legacy_aliases_remain_source_compatible() {
- use radroots_core::{
- RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCorePercent,
- RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError,
- RadrootsCoreQuantityPriceOps, 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 price = RadrootsCoreQuantityPrice::new(money, quantity);
- let _ = RadrootsCoreQuantityPriceOps::try_cost_for(
- &price,
- &RadrootsCoreQuantity::new(decimal, RadrootsCoreUnit::Each),
- );
- let _ = RadrootsCoreQuantityPriceError::PerQuantityZero;
- let _ = RadrootsCoreUnitDimension::Count;
+fn canonical_values_use_checked_construction() {
+ let money = Money::try_new(Decimal::ONE, Currency::USD).expect("valid money");
+ let quantity = Quantity::try_new(Decimal::ONE, Unit::Each).expect("valid quantity");
+ let price = QuantityPrice::try_new(money, quantity).expect("valid price");
+ assert_eq!(price.quantity().unit(), Unit::Each);
}
diff --git a/crates/core/tests/percent.rs b/crates/core/tests/percent.rs
@@ -5,13 +5,6 @@ use core::str::FromStr;
use radroots_core::{Decimal, Percent, decimal, money, percent::ParseError};
#[test]
-fn ratio_roundtrip() {
- 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 checked_ratio_paths_report_overflow_and_roundtrip() {
assert_eq!(
Percent::try_from_ratio(Decimal::MAX),
@@ -25,36 +18,26 @@ fn checked_ratio_paths_report_overflow_and_roundtrip() {
}
#[test]
-fn parses_percent_strings() {
- let pct = Percent::from_str("12.5%").unwrap();
- assert_eq!(pct.value, common::dec("12.5"));
-
- let pct = Percent::from_str(" 12.5 ").unwrap();
- assert_eq!(pct.value, common::dec("12.5"));
-
+fn parsing_and_accessors_preserve_signed_values() {
+ let percent = Percent::from_str("12.5%").unwrap();
+ assert_eq!(percent.value(), common::dec("12.5"));
+ assert_eq!(Percent::from_str(" 12.5 ").unwrap(), percent);
assert_eq!(Percent::from_str("nope"), Err(ParseError::InvalidNumber));
}
#[test]
-fn of_money_and_quantized() {
+fn checked_money_calculation_and_quantization_are_deterministic() {
let base = common::money("20.00", "USD");
- let pct = Percent::from_str("10").unwrap();
- let out = pct.of_money(&base);
- assert_eq!(out.amount, common::dec("2.00"));
+ let percent = Percent::from_str("10").unwrap();
+ assert_eq!(
+ percent.try_of_money(&base).unwrap().amount(),
+ common::dec("2")
+ );
let tiny = common::money("0.05", "USD");
- let pct = Percent::from_str("10").unwrap();
- let rounded = pct.of_money_quantized(&tiny);
- assert_eq!(rounded.amount, common::dec("0.01"));
-}
-
-#[test]
-fn checked_money_calculation_rejects_invalid_base_and_overflow() {
- let negative = common::money("-10", "USD");
- let pct = Percent::new(common::dec("10"));
assert_eq!(
- pct.try_of_money(&negative),
- Err(money::Error::NegativeAmount)
+ percent.try_of_money_quantized(&tiny).unwrap().amount(),
+ common::dec("0.01")
);
let max = radroots_core::Money::try_new(Decimal::MAX, radroots_core::Currency::USD).unwrap();
@@ -66,9 +49,8 @@ fn checked_money_calculation_rejects_invalid_base_and_overflow() {
}
#[test]
-fn display_and_parse_error_display_paths_are_exercised() {
- let pct = Percent::from_str("12.5%").unwrap();
- assert_eq!(pct.to_string(), "12.5%");
+fn display_and_parse_error_messages_are_stable() {
+ assert_eq!(Percent::from_str("12.5%").unwrap().to_string(), "12.5%");
assert_eq!(
ParseError::InvalidNumber.to_string(),
"invalid percent string"
diff --git a/crates/core/tests/quantity.rs b/crates/core/tests/quantity.rs
@@ -3,156 +3,103 @@ mod common;
use radroots_core::{Decimal, Quantity, Unit, quantity::Error};
#[test]
-fn zero_helpers_and_scale_paths_are_exercised() {
- let zero = radroots_core::Quantity::zero(Unit::MassKg);
+fn zero_and_checked_constructor_preserve_invariants() {
+ let zero = Quantity::zero(Unit::MassKg);
assert!(zero.is_zero());
+ assert_eq!(zero.amount(), Decimal::ZERO);
+ assert_eq!(zero.unit(), Unit::MassKg);
assert_eq!(zero.canonical_unit(), Unit::MassG);
assert!(!zero.is_canonical());
- 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", 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", Unit::Each).with_optional_label(Some("case"));
- assert_eq!(q.label.as_deref(), Some("case"));
-
- let q = q.with_optional_label::<&str>(None);
- assert!(q.label.is_none());
-}
-
-#[test]
-fn ensure_non_negative_rejects_negative_amount() {
- let q = common::qty("-1", Unit::Each);
- assert_eq!(q.ensure_non_negative(), Err(Error::NegativeAmount));
-}
-
-#[test]
-fn ensure_non_negative_accepts_non_negative_amount() {
- let q = common::qty("0", Unit::Each);
- assert_eq!(q.ensure_non_negative(), Ok(()));
-}
-
-#[test]
-fn checked_constructor_rejects_negative_and_exposes_fields() {
assert_eq!(
Quantity::try_new(common::dec("-1"), Unit::Each),
Err(Error::NegativeAmount)
);
- let quantity = Quantity::try_new(common::dec("2"), Unit::Each)
- .unwrap()
- .with_label("bundle");
- assert_eq!(quantity.amount(), common::dec("2"));
- assert_eq!(quantity.unit(), Unit::Each);
- assert_eq!(quantity.label(), Some("bundle"));
}
#[test]
-fn try_add_and_try_sub_require_matching_units() {
- 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"));
- assert_eq!(sum.label.as_deref(), Some("lhs"));
-
- assert_eq!(a.try_add(&c), Err(Error::UnitMismatch));
- assert_eq!(b.try_sub(&c), Err(Error::UnitMismatch));
+fn label_helpers_set_replace_and_clear() {
+ let quantity = common::qty("1", Unit::Each).with_label("box");
+ assert_eq!(quantity.label(), Some("box"));
+ let quantity = quantity.with_optional_label(Some("case"));
+ assert_eq!(quantity.label(), Some("case"));
+ let quantity = quantity.with_optional_label::<&str>(None);
+ assert_eq!(quantity.label(), None);
+ assert_eq!(quantity.with_label("bag").clear_label().label(), None);
}
#[test]
-fn try_sub_success_path_is_exercised() {
- 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"));
-}
+fn checked_addition_and_subtraction_require_matching_units() {
+ let first = common::qty("5", Unit::Each).with_label("lhs");
+ let second = common::qty("2", Unit::Each);
+ let kilograms = common::qty("1", Unit::MassKg);
-#[test]
-fn checked_add_and_sub_return_none_on_mismatch() {
- 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());
-}
+ let sum = first.try_add(&second).unwrap();
+ assert_eq!(sum.amount(), common::dec("7"));
+ assert_eq!(sum.label(), Some("lhs"));
+ let difference = first.try_sub(&second).unwrap();
+ assert_eq!(difference.amount(), common::dec("3"));
+ assert_eq!(difference.label(), Some("lhs"));
-#[test]
-fn checked_add_and_sub_return_some_on_matching_units() {
- 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"));
-
- let subbed = a.checked_sub(&b).expect("subbed quantity");
- assert_eq!(subbed.amount, common::dec("3"));
- assert_eq!(subbed.label.as_deref(), Some("lhs"));
+ assert_eq!(first.try_add(&kilograms), Err(Error::UnitMismatch));
+ assert_eq!(first.try_sub(&kilograms), Err(Error::UnitMismatch));
+ assert!(first.checked_add(&kilograms).is_none());
+ assert!(first.checked_sub(&kilograms).is_none());
+ assert_eq!(second.try_sub(&first), Err(Error::NegativeAmount));
}
#[test]
-fn checked_arithmetic_reports_negative_overflow_and_zero_division() {
- let one = Quantity::try_new(Decimal::ONE, Unit::Each).unwrap();
- let two = Quantity::try_new(Decimal::from(2u32), Unit::Each).unwrap();
- assert_eq!(one.try_sub(&two), Err(Error::NegativeAmount));
+fn checked_scaling_reports_negative_overflow_and_zero_division() {
+ let quantity = common::qty("2", Unit::Each).with_label("unit");
+ let scaled = quantity.checked_mul_decimal(common::dec("2.5")).unwrap();
+ assert_eq!(scaled.amount(), common::dec("5"));
+ assert_eq!(scaled.unit(), Unit::Each);
+ assert_eq!(scaled.label(), Some("unit"));
- let max = Quantity::try_new(Decimal::MAX, Unit::Each).unwrap();
- assert_eq!(max.try_add(&one), Err(Error::ArithmeticOverflow));
+ let divided = quantity.checked_div_decimal(common::dec("2")).unwrap();
+ assert_eq!(divided.amount(), common::dec("1"));
assert_eq!(
- one.checked_div_decimal(Decimal::ZERO),
- Err(Error::DivisionByZero)
+ quantity.checked_mul_decimal(common::dec("-1")),
+ Err(Error::NegativeAmount)
);
assert_eq!(
- one.checked_mul_decimal(common::dec("-1")),
- Err(Error::NegativeAmount)
+ quantity.checked_div_decimal(Decimal::ZERO),
+ Err(Error::DivisionByZero)
);
-}
-#[test]
-fn mul_and_div_preserve_unit_and_label() {
- 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, 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, Unit::Each);
- assert_eq!(divided.label.as_deref(), Some("unit"));
+ let max = Quantity::try_new(Decimal::MAX, Unit::Each).unwrap();
+ assert_eq!(max.try_add(&quantity), Err(Error::ArithmeticOverflow));
}
#[test]
-fn mul_and_div_operator_impls_are_exercised() {
- 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"));
-
- let div = qty / common::dec("2");
- assert_eq!(div.amount, common::dec("2"));
- assert_eq!(div.label.as_deref(), Some("bag"));
-}
+fn conversion_is_checked_and_preserves_labels() {
+ let kilograms = common::qty("1", Unit::MassKg).with_label("bag");
+ let grams = kilograms.try_convert_to(Unit::MassG).unwrap();
+ assert_eq!(grams.amount(), common::dec("1000"));
+ assert_eq!(grams.unit(), Unit::MassG);
+ assert_eq!(grams.label(), Some("bag"));
+ assert!(grams.is_canonical());
-#[test]
-fn display_includes_label_when_present() {
- let q = common::qty("1.5", Unit::Each).with_label("bag");
- assert_eq!(q.to_string(), "1.5 each (bag)");
+ let canonical = common::qty("2", Unit::VolumeL).to_canonical().unwrap();
+ assert_eq!(canonical.unit(), Unit::VolumeMl);
+ assert_eq!(canonical.amount(), common::dec("2000"));
+
+ assert_eq!(
+ common::qty("1", Unit::Each).try_convert_to(Unit::MassG),
+ Err(radroots_core::unit::ConvertError::NotConvertibleUnits {
+ from: Unit::Each,
+ to: Unit::MassG,
+ })
+ );
}
#[test]
-fn display_without_label_and_error_display_are_exercised() {
- let q = common::qty("1.5", Unit::Each);
- assert_eq!(q.to_string(), "1.5 each");
-
+fn display_and_error_messages_are_stable() {
+ assert_eq!(
+ common::qty("1.5", Unit::Each).with_label("bag").to_string(),
+ "1.5 each (bag)"
+ );
+ assert_eq!(common::qty("1.5", Unit::Each).to_string(), "1.5 each");
assert_eq!(
Error::NegativeAmount.to_string(),
"quantity amount must be ≥ 0"
@@ -172,74 +119,13 @@ fn display_without_label_and_error_display_are_exercised() {
fn display_propagates_formatter_errors() {
use core::fmt::{self, Write};
- struct FailWriter {
- fail_on_paren: bool,
- fail_on_call: usize,
- calls: usize,
- }
-
+ struct FailWriter;
impl Write for FailWriter {
- fn write_str(&mut self, s: &str) -> fmt::Result {
- self.calls += 1;
- if self.fail_on_paren && s.contains('(') {
- return Err(fmt::Error);
- }
- if self.calls == self.fail_on_call {
- Err(fmt::Error)
- } else {
- Ok(())
- }
+ fn write_str(&mut self, _: &str) -> fmt::Result {
+ Err(fmt::Error)
}
}
- 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,
- calls: 0,
- };
- assert!(fmt::write(&mut first_write_fails, format_args!("{with_label}")).is_err());
-
- let mut second_write_fails = FailWriter {
- fail_on_paren: true,
- fail_on_call: usize::MAX,
- calls: 0,
- };
- assert!(fmt::write(&mut second_write_fails, format_args!("{with_label}")).is_err());
-}
-
-#[test]
-fn try_convert_to_changes_unit_and_amount() {
- 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, Unit::MassG);
-}
-
-#[test]
-fn to_canonical_converts_mass_and_volume() {
- let q = common::qty("2", Unit::VolumeL);
- let canonical = q.to_canonical().unwrap();
- 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", Unit::Each);
- let err = q.try_convert_to(Unit::MassG).unwrap_err();
- assert_eq!(
- err,
- radroots_core::unit::ConvertError::NotConvertibleUnits {
- from: Unit::Each,
- to: Unit::MassG
- }
- );
-}
-
-#[test]
-fn try_convert_to_same_unit_returns_self_clone() {
- 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);
+ let quantity = common::qty("1.5", Unit::Each).with_label("bag");
+ assert!(fmt::write(&mut FailWriter, format_args!("{quantity}")).is_err());
}
diff --git a/crates/core/tests/quantity_price.rs b/crates/core/tests/quantity_price.rs
@@ -5,84 +5,56 @@ use radroots_core::{
pricing::{Error, QuantityPriceOps},
};
-#[test]
-fn cost_for_scales_by_ratio() {
- 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"));
+fn unit_price(amount: &str, per: &str, unit: Unit) -> QuantityPrice {
+ QuantityPrice::try_new(common::money(amount, "USD"), common::qty(per, unit))
+ .expect("valid price")
}
#[test]
-fn cost_for_returns_zero_on_unit_mismatch() {
- 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());
-}
+fn checked_cost_scales_by_ratio_and_rounds_deterministically() {
+ let price = unit_price("10", "1", Unit::MassKg);
+ let cost = price.try_cost_for(&common::qty("2", Unit::MassKg)).unwrap();
+ assert_eq!(cost.amount(), common::dec("20"));
-#[test]
-fn cost_for_rounded_and_quantized_price_differ() {
- 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);
-
- assert_eq!(rounded.amount, common::dec("2.01"));
- assert_eq!(quantized.amount, common::dec("2.02"));
+ let rounded = unit_price("1.005", "1", Unit::Each)
+ .try_cost_for_rounded(&common::qty("2", Unit::Each))
+ .unwrap();
+ assert_eq!(rounded.amount(), common::dec("2.01"));
}
#[test]
-fn try_cost_for_validates_quantity_and_units() {
- 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));
-
+fn checked_constructor_rejects_zero_per_quantity() {
assert_eq!(
- zero_price.try_cost_for(&common::qty("1", Unit::Each)),
+ QuantityPrice::try_new(common::money("1", "USD"), Quantity::zero(Unit::Each)),
Err(Error::PerQuantityZero)
);
+}
+
+#[test]
+fn checked_cost_never_silently_converts_failures_to_zero() {
+ let price = unit_price("10", "1", Unit::Each);
assert_eq!(
price.try_cost_for(&common::qty("1", Unit::MassKg)),
Err(Error::UnitMismatch {
have: Unit::MassKg,
- want: Unit::Each
+ want: Unit::Each,
})
);
-}
-
-#[test]
-fn checked_constructor_rejects_invalid_price_shapes() {
- assert_eq!(
- QuantityPrice::try_new(common::money("-1", "USD"), common::qty("1", Unit::Each),),
- Err(Error::NegativePrice)
- );
assert_eq!(
- QuantityPrice::try_new(common::money("1", "USD"), common::qty("0", Unit::Each),),
- Err(Error::PerQuantityZero)
- );
- assert_eq!(
- QuantityPrice::try_new(common::money("1", "USD"), common::qty("-1", Unit::Each),),
- Err(Error::PerQuantityNegative)
+ price.try_cost_for_amount_in(common::dec("-1"), Unit::Each),
+ Err(Error::NegativeRequestedQuantity)
);
-}
-
-#[test]
-fn checked_cost_paths_never_silently_convert_errors_to_zero() {
- let price =
- QuantityPrice::try_new(common::money("10", "USD"), common::qty("1", Unit::Each)).unwrap();
assert_eq!(
- price.try_cost_for(&common::qty("1", Unit::MassKg)),
+ price.try_cost_for_rounded(&common::qty("1", Unit::MassKg)),
Err(Error::UnitMismatch {
have: Unit::MassKg,
want: Unit::Each,
})
);
- assert_eq!(
- price.try_cost_for(&common::qty("-1", Unit::Each)),
- Err(Error::NegativeRequestedQuantity)
- );
}
#[test]
-fn checked_cost_paths_report_arithmetic_overflow() {
+fn checked_cost_reports_arithmetic_overflow() {
let price = QuantityPrice::try_new(
Money::try_new(Decimal::MAX, Currency::USD).unwrap(),
Quantity::try_new(Decimal::ONE, Unit::Each).unwrap(),
@@ -95,148 +67,72 @@ fn checked_cost_paths_report_arithmetic_overflow() {
}
#[test]
-fn try_cost_for_rounded_error_path_is_exercised() {
- let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::Each));
+fn amount_and_quantity_conversion_support_mass_and_volume() {
+ let mass = unit_price("10", "1", Unit::MassKg);
assert_eq!(
- price.try_cost_for_rounded(&common::qty("1", Unit::MassKg)),
- Err(Error::UnitMismatch {
- have: Unit::MassKg,
- want: Unit::Each
- })
+ mass.try_cost_for_amount_in(common::dec("500"), Unit::MassG)
+ .unwrap()
+ .amount(),
+ common::dec("5")
+ );
+ assert_eq!(
+ mass.try_cost_for_quantity_in(&common::qty("250", Unit::MassG))
+ .unwrap()
+ .amount(),
+ common::dec("2.5")
);
-}
-
-#[test]
-fn try_cost_for_amount_in_converts_mass_units() {
- let price = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg));
- let cost = price
- .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 = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::VolumeL));
- let cost = price
- .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 = QuantityPrice::new(common::money("10", "USD"), common::qty("1", Unit::MassKg));
assert_eq!(
- price.try_cost_for_amount_in(common::dec("1"), Unit::Each),
+ mass.try_cost_for_amount_in(common::dec("1"), Unit::Each),
Err(Error::NonConvertibleUnits {
from: Unit::Each,
- to: Unit::MassKg
+ to: Unit::MassKg,
})
);
-}
-#[test]
-fn try_cost_for_amount_in_same_unit_path_is_exercised() {
- let price = QuantityPrice::new(common::money("4", "USD"), common::qty("1", Unit::Each));
- let out = price
- .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 = 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 = QuantityPrice::new(common::money("10", "USD"), common::qty("0", Unit::MassKg));
+ let volume = unit_price("10", "1", Unit::VolumeL);
assert_eq!(
- zero.try_to_unit_price(Unit::MassG),
- Err(Error::PerQuantityZero)
+ volume
+ .try_cost_for_amount_in(common::dec("500"), Unit::VolumeMl)
+ .unwrap()
+ .amount(),
+ common::dec("5")
);
+}
- let base = QuantityPrice::new(common::money("5", "USD"), common::qty("2", Unit::MassKg));
+#[test]
+fn unit_price_conversion_is_checked_and_uses_accessors() {
+ let base = unit_price("5", "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(Unit::VolumeMl).unwrap_err();
+ assert_eq!(same.quantity().unit(), Unit::MassKg);
+ assert_eq!(same.quantity().amount(), Decimal::ONE);
+ assert_eq!(same.amount().amount(), common::dec("2.5"));
assert_eq!(
- err,
- Error::NonConvertibleUnits {
+ base.try_to_unit_price(Unit::VolumeMl),
+ Err(Error::NonConvertibleUnits {
from: Unit::MassKg,
- to: Unit::VolumeMl
- }
- );
-}
-
-#[test]
-fn cost_for_and_quantized_price_zero_paths_are_exercised() {
- 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 = QuantityPrice::new(common::money("3.33", "USD"), common::qty("0", Unit::Each));
- assert!(
- zero_per
- .cost_for(&common::qty("1", Unit::Each))
- .amount
- .is_zero()
- );
- assert!(
- zero_per
- .cost_for_with_quantized_price(&common::qty("1", Unit::Each))
- .amount
- .is_zero()
+ to: Unit::VolumeMl,
+ })
);
- let mismatch_qty = common::qty("1", Unit::MassG);
- assert!(
- p.cost_for_with_quantized_price(&mismatch_qty)
- .amount
- .is_zero()
- );
+ let canonical = unit_price("6.99", "1", Unit::MassLb)
+ .try_to_canonical_unit_price()
+ .unwrap();
+ assert_eq!(canonical.quantity().unit(), Unit::MassG);
+ assert_eq!(canonical.quantity().amount(), Decimal::ONE);
+ assert!(canonical.is_price_per_canonical_unit());
}
#[test]
-fn try_to_unit_price_detects_underflow_to_zero_normalized_amount() {
- let tiny = QuantityPrice::new(
- common::money("1", "USD"),
- common::qty("0.0000000000000000000000000001", Unit::VolumeMl),
+fn unit_price_conversion_detects_precision_underflow() {
+ let tiny = unit_price("1", "0.0000000000000000000000000001", Unit::VolumeMl);
+ assert_eq!(
+ tiny.try_to_unit_price(Unit::VolumeL),
+ Err(Error::PerQuantityZero)
);
- let err = tiny.try_to_unit_price(Unit::VolumeL).unwrap_err();
- assert_eq!(err, Error::PerQuantityZero);
-}
-
-#[test]
-fn try_to_canonical_unit_price_converts_units() {
- 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, 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);
-}
-
-#[test]
-fn is_price_per_canonical_unit_detects_canonical() {
- let price = QuantityPrice::new(common::money("1.00", "USD"), common::qty("1", Unit::MassG));
- assert!(price.is_price_per_canonical_unit());
-
- let price = QuantityPrice::new(common::money("1.00", "USD"), common::qty("1", Unit::MassKg));
- assert!(!price.is_price_per_canonical_unit());
}
#[test]
-fn pricing_error_display_is_stable() {
+fn pricing_error_messages_are_stable() {
assert_eq!(
Error::PerQuantityZero.to_string(),
"price quantity must be greater than zero"
diff --git a/crates/core/tests/serde.rs b/crates/core/tests/serde.rs
@@ -23,7 +23,7 @@ fn decimal_serializes_as_string() {
#[test]
fn quantity_uses_decimal_str_and_omits_empty_label() {
- let q = Quantity::new(common::dec("1.2300"), Unit::MassKg);
+ let q = Quantity::try_new(common::dec("1.2300"), Unit::MassKg).unwrap();
let value = serde_json::to_value(&q).unwrap();
assert_eq!(value["amount"], Value::String("1.23".to_string()));
@@ -35,9 +35,9 @@ fn quantity_uses_decimal_str_and_omits_empty_label() {
fn quantity_deserializes_decimal_str_via_serde_ext() {
let raw = r#"{"amount":"1.2300","unit":"kg","label":"bag"}"#;
let q: Quantity = serde_json::from_str(raw).unwrap();
- assert_eq!(q.amount, common::dec("1.23"));
- assert_eq!(q.unit, Unit::MassKg);
- assert_eq!(q.label.as_deref(), Some("bag"));
+ assert_eq!(q.amount(), common::dec("1.23"));
+ assert_eq!(q.unit(), Unit::MassKg);
+ assert_eq!(q.label(), Some("bag"));
}
#[test]
@@ -49,7 +49,7 @@ fn quantity_rejects_non_string_decimal_amount() {
#[test]
fn money_and_percent_roundtrip_with_strings() {
- let money = Money::new(common::dec("2.50"), Currency::USD);
+ let money = Money::try_new(common::dec("2.50"), Currency::USD).unwrap();
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()));
@@ -60,6 +60,34 @@ fn money_and_percent_roundtrip_with_strings() {
}
#[test]
+fn native_value_deserialization_enforces_invariants() {
+ let negative_money = r#"{"amount":"-0.01","currency":"USD"}"#;
+ assert!(
+ serde_json::from_str::<Money>(negative_money)
+ .unwrap_err()
+ .to_string()
+ .contains("money amount")
+ );
+
+ let negative_quantity = r#"{"amount":"-1","unit":"each"}"#;
+ assert!(
+ serde_json::from_str::<Quantity>(negative_quantity)
+ .unwrap_err()
+ .to_string()
+ .contains("quantity amount")
+ );
+
+ let zero_price =
+ r#"{"amount":{"amount":"1","currency":"USD"},"quantity":{"amount":"0","unit":"each"}}"#;
+ assert!(
+ serde_json::from_str::<QuantityPrice>(zero_price)
+ .unwrap_err()
+ .to_string()
+ .contains("greater than zero")
+ );
+}
+
+#[test]
fn currency_serializes_as_code() {
let c = Currency::from_str("usd").unwrap();
let json = serde_json::to_string(&c).unwrap();
diff --git a/crates/event/src/order.rs b/crates/event/src/order.rs
@@ -555,7 +555,7 @@ fn validate_economic_item(
.ok_or(RadrootsOrderPayloadError::InvalidEconomicItemSubtotal { index })?;
let expected_subtotal = checked_decimal_mul(item.unit_price_amount, quantity_total)
.ok_or(RadrootsOrderPayloadError::InvalidEconomicItemSubtotal { index })?;
- if item.line_subtotal.amount != expected_subtotal {
+ if item.line_subtotal.amount() != expected_subtotal {
return Err(RadrootsOrderPayloadError::InvalidEconomicItemSubtotal { index });
}
Ok(item.line_subtotal.clone())
@@ -624,10 +624,10 @@ fn validate_economic_line(
) -> Result<(), RadrootsOrderPayloadError> {
validate_required_field(&line.id, "economics.line.id")?;
validate_required_field(&line.reason, "economics.line.reason")?;
- if line.amount.currency != expected_currency {
+ if line.amount.currency() != expected_currency {
return Err(RadrootsOrderPayloadError::InvalidEconomicCurrency { field });
}
- if line.amount.amount.is_zero() || line.amount.amount.is_sign_negative() {
+ if line.amount.amount().is_zero() || line.amount.amount().is_sign_negative() {
return Err(RadrootsOrderPayloadError::InvalidEconomicLineAmount { field, index });
}
Ok(())
@@ -663,10 +663,10 @@ fn validate_total_money(
expected_currency: Currency,
field: &'static str,
) -> Result<(), RadrootsOrderPayloadError> {
- if money.currency != expected_currency {
+ if money.currency() != expected_currency {
return Err(RadrootsOrderPayloadError::InvalidEconomicCurrency { field });
}
- if money.amount.is_sign_negative() {
+ if money.amount().is_sign_negative() {
return Err(RadrootsOrderPayloadError::InvalidEconomicTotal { field });
}
Ok(())
@@ -677,25 +677,25 @@ fn validate_total_matches(
expected: &Money,
field: &'static str,
) -> Result<(), RadrootsOrderPayloadError> {
- if actual.currency != expected.currency {
+ if actual.currency() != expected.currency() {
return Err(RadrootsOrderPayloadError::InvalidEconomicCurrency { field });
}
- if actual.amount != expected.amount {
+ if actual.amount() != expected.amount() {
return Err(RadrootsOrderPayloadError::InvalidEconomicTotal { field });
}
Ok(())
}
fn checked_decimal_add(left: Decimal, right: Decimal) -> Option<Decimal> {
- left.0.checked_add(right.0).map(Decimal)
+ left.checked_add(right).ok()
}
fn checked_decimal_sub(left: Decimal, right: Decimal) -> Option<Decimal> {
- left.0.checked_sub(right.0).map(Decimal)
+ left.checked_sub(right).ok()
}
fn checked_decimal_mul(left: Decimal, right: Decimal) -> Option<Decimal> {
- left.0.checked_mul(right.0).map(Decimal)
+ left.checked_mul(right).ok()
}
fn checked_money_add(
@@ -703,12 +703,13 @@ fn checked_money_add(
right: &Money,
field: &'static str,
) -> Result<Money, RadrootsOrderPayloadError> {
- if left.currency != right.currency {
+ if left.currency() != right.currency() {
return Err(RadrootsOrderPayloadError::InvalidEconomicCurrency { field });
}
- let amount = checked_decimal_add(left.amount, right.amount)
+ let amount = checked_decimal_add(left.amount(), right.amount())
.ok_or(RadrootsOrderPayloadError::InvalidEconomicTotal { field })?;
- Ok(Money::new(amount, left.currency))
+ Money::try_new(amount, left.currency())
+ .map_err(|_| RadrootsOrderPayloadError::InvalidEconomicTotal { field })
}
fn checked_money_sub_non_negative(
@@ -716,15 +717,16 @@ fn checked_money_sub_non_negative(
right: &Money,
field: &'static str,
) -> Result<Money, RadrootsOrderPayloadError> {
- if left.currency != right.currency {
+ if left.currency() != right.currency() {
return Err(RadrootsOrderPayloadError::InvalidEconomicCurrency { field });
}
- let amount = checked_decimal_sub(left.amount, right.amount)
+ let amount = checked_decimal_sub(left.amount(), right.amount())
.ok_or(RadrootsOrderPayloadError::InvalidEconomicTotal { field })?;
if amount.is_sign_negative() {
return Err(RadrootsOrderPayloadError::InvalidEconomicTotal { field });
}
- Ok(Money::new(amount, left.currency))
+ Money::try_new(amount, left.currency())
+ .map_err(|_| RadrootsOrderPayloadError::InvalidEconomicTotal { field })
}
fn validate_inventory_commitments(
@@ -799,7 +801,11 @@ mod tests {
}
fn usd(raw: &str) -> Money {
- Money::new(decimal(raw), Currency::USD)
+ money(raw, Currency::USD)
+ }
+
+ fn money(raw: &str, currency: Currency) -> Money {
+ Money::try_new(decimal(raw), currency).unwrap()
}
fn sample_order_economics() -> RadrootsOrderEconomics {
@@ -1123,7 +1129,7 @@ mod tests {
);
let mut economics = sample_order_economics();
- economics.adjustments[0].amount = Money::new(decimal("2"), Currency::EUR);
+ economics.adjustments[0].amount = money("2", Currency::EUR);
assert_eq!(
economics.validate().unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency {
@@ -1149,7 +1155,7 @@ mod tests {
);
let mut economics = sample_order_economics();
- economics.items[0].line_subtotal = Money::new(decimal("12"), Currency::EUR);
+ economics.items[0].line_subtotal = money("12", Currency::EUR);
assert_eq!(
economics.validate().unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency {
@@ -1206,20 +1212,13 @@ mod tests {
);
let mut economics = sample_order_economics();
- economics.subtotal = Money::new(decimal("18"), Currency::EUR);
+ economics.subtotal = money("18", Currency::EUR);
assert_eq!(
economics.validate().unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency { field: "subtotal" }
);
let mut economics = sample_order_economics();
- economics.subtotal = usd("-1");
- assert_eq!(
- economics.validate().unwrap_err(),
- RadrootsOrderPayloadError::InvalidEconomicTotal { field: "subtotal" }
- );
-
- let mut economics = sample_order_economics();
economics.discount_total = usd("4");
assert_eq!(
economics.validate().unwrap_err(),
@@ -1319,45 +1318,21 @@ mod tests {
index: 0
}
);
-
- let mut economics = sample_order_economics();
- economics.adjustments[0].amount = usd("-1");
- assert_eq!(
- economics.validate().unwrap_err(),
- RadrootsOrderPayloadError::InvalidEconomicLineAmount {
- field: "adjustments",
- index: 0
- }
- );
}
#[test]
fn order_economics_helpers_cover_currency_error_paths() {
assert_eq!(
- validate_total_money(&usd("-1"), Currency::USD, "subtotal").unwrap_err(),
- RadrootsOrderPayloadError::InvalidEconomicTotal { field: "subtotal" }
- );
- assert_eq!(
- validate_total_matches(&usd("1"), &Money::new(decimal("1"), Currency::EUR), "total")
- .unwrap_err(),
+ validate_total_matches(&usd("1"), &money("1", Currency::EUR), "total").unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency { field: "total" }
);
assert_eq!(
- checked_money_add(
- &usd("1"),
- &Money::new(decimal("1"), Currency::EUR),
- "subtotal"
- )
- .unwrap_err(),
+ checked_money_add(&usd("1"), &money("1", Currency::EUR), "subtotal").unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency { field: "subtotal" }
);
assert_eq!(
- checked_money_sub_non_negative(
- &usd("1"),
- &Money::new(decimal("1"), Currency::EUR),
- "total"
- )
- .unwrap_err(),
+ checked_money_sub_non_negative(&usd("1"), &money("1", Currency::EUR), "total")
+ .unwrap_err(),
RadrootsOrderPayloadError::InvalidEconomicCurrency { field: "total" }
);
}
diff --git a/crates/event_codec/src/farm/mod.rs b/crates/event_codec/src/farm/mod.rs
@@ -496,11 +496,12 @@ mod tests {
primary_bin_id: bin_id("bin-1"),
bins: vec![RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1u32), Unit::Each),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::Each),
- ),
+ quantity: Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/src/operational_listing/decode.rs b/crates/event_codec/src/operational_listing/decode.rs
@@ -472,7 +472,10 @@ fn decode_operational_listing_from_event_parts(
if bin.quantity.is_some() {
return Err(OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_BIN));
}
- bin.quantity = Some(Quantity::new(amount, unit));
+ bin.quantity =
+ Some(Quantity::try_new(amount, unit).map_err(|_| {
+ OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_BIN)
+ })?);
match tag.as_slice() {
[_, _, _, _, display_amount_raw, display_unit_raw]
@@ -504,10 +507,12 @@ fn decode_operational_listing_from_event_parts(
let currency = parse_currency(&tag[3], TAG_RADROOTS_PRICE)?;
let per_amount = parse_decimal(&tag[4], TAG_RADROOTS_PRICE)?;
let per_unit = parse_unit(&tag[5], TAG_RADROOTS_PRICE)?;
- let price_per_canonical_unit = QuantityPrice::new(
- Money::new(amount, currency),
- Quantity::new(per_amount, per_unit),
- );
+ let money = Money::try_new(amount, currency)
+ .map_err(|_| OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_PRICE))?;
+ let quantity = Quantity::try_new(per_amount, per_unit)
+ .map_err(|_| OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_PRICE))?;
+ let price_per_canonical_unit = QuantityPrice::try_new(money, quantity)
+ .map_err(|_| OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_PRICE))?;
if !price_per_canonical_unit.is_price_per_canonical_unit() {
return Err(OperationalListingDecodeError::InvalidTag(
TAG_RADROOTS_PRICE,
@@ -530,7 +535,10 @@ fn decode_operational_listing_from_event_parts(
[_, _, _, _, _, _, display_price_raw, display_unit_raw] => {
let display_price = parse_decimal(display_price_raw, TAG_RADROOTS_PRICE)?;
let display_unit = parse_unit(display_unit_raw, TAG_RADROOTS_PRICE)?;
- bin.display_price = Some(Money::new(display_price, currency));
+ bin.display_price =
+ Some(Money::try_new(display_price, currency).map_err(|_| {
+ OperationalListingDecodeError::InvalidTag(TAG_RADROOTS_PRICE)
+ })?);
bin.display_price_unit = Some(display_unit);
}
_ => {}
@@ -868,7 +876,7 @@ fn build_bins(
.ok_or(OperationalListingDecodeError::MissingTag(
TAG_RADROOTS_PRICE,
))?;
- if quantity.unit != price.quantity.unit {
+ if quantity.unit() != price.quantity().unit() {
return Err(OperationalListingDecodeError::InvalidTag(
TAG_RADROOTS_PRICE,
));
diff --git a/crates/event_codec/src/operational_listing/encode.rs b/crates/event_codec/src/operational_listing/encode.rs
@@ -104,11 +104,12 @@ mod tests {
primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"),
bins: vec![RadrootsOperationalListingBin {
bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"),
- quantity: Quantity::new(decimal("1"), Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(decimal("1"), Currency::USD),
- Quantity::new(Decimal::ONE, Unit::MassG),
- ),
+ quantity: Quantity::try_new(decimal("1"), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(decimal("1"), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::ONE, Unit::MassG).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/src/operational_listing/tags.rs b/crates/event_codec/src/operational_listing/tags.rs
@@ -322,14 +322,14 @@ fn tag_listing_plot(plot: &RadrootsPlotRef) -> Result<Vec<String>, EventEncodeEr
}
fn tag_listing_bin(bin: &RadrootsOperationalListingBin) -> Result<Vec<String>, EventEncodeError> {
- let unit = bin.quantity.unit;
+ let unit = bin.quantity.unit();
if unit != unit.canonical_unit() {
return Err(EventEncodeError::EmptyRequiredField("bin.quantity"));
}
let mut tag = Vec::with_capacity(7);
tag.push(TAG_RADROOTS_BIN.to_string());
tag.push(bin.bin_id.to_string());
- tag.push(bin.quantity.amount.to_string());
+ tag.push(bin.quantity.amount().to_string());
tag.push(unit.code().to_string());
if let Some(amount) = bin.display_amount.as_ref() {
let Some(unit) = bin.display_unit else {
@@ -341,7 +341,7 @@ fn tag_listing_bin(bin: &RadrootsOperationalListingBin) -> Result<Vec<String>, E
.display_label
.as_deref()
.and_then(clean_value)
- .or_else(|| bin.quantity.label.as_deref().and_then(clean_value))
+ .or_else(|| bin.quantity.label().and_then(clean_value))
{
tag.push(label);
}
@@ -361,16 +361,16 @@ fn tag_listing_price(bin: &RadrootsOperationalListingBin) -> Result<Vec<String>,
let mut tag = Vec::with_capacity(8);
tag.push(TAG_RADROOTS_PRICE.to_string());
tag.push(bin.bin_id.to_string());
- tag.push(price.amount.amount.to_string());
- tag.push(price.amount.currency.as_str().to_string());
- tag.push(price.quantity.amount.to_string());
- tag.push(price.quantity.unit.code().to_string());
+ tag.push(price.amount().amount().to_string());
+ tag.push(price.amount().currency().as_str().to_string());
+ tag.push(price.quantity().amount().to_string());
+ tag.push(price.quantity().unit().code().to_string());
match (&bin.display_price, bin.display_price_unit) {
(Some(price_display), Some(unit)) => {
- if price_display.currency != price.amount.currency {
+ if price_display.currency() != price.amount().currency() {
return Err(EventEncodeError::EmptyRequiredField("bin.display_price"));
}
- tag.push(price_display.amount.to_string());
+ tag.push(price_display.amount().to_string());
tag.push(unit.code().to_string());
}
(None, None) => {}
@@ -393,8 +393,8 @@ fn bin_total_price(bin: &RadrootsOperationalListingBin) -> Result<Money, EventEn
fn tag_listing_price_generic(price: &Money) -> Vec<String> {
let mut tag = Vec::with_capacity(4);
tag.push(TAG_PRICE.to_string());
- tag.push(price.amount.to_string());
- tag.push(price.currency.as_str().to_string());
+ tag.push(price.amount().to_string());
+ tag.push(price.currency().as_str().to_string());
tag
}
@@ -481,6 +481,18 @@ mod tests {
Decimal::from_str(value).expect("valid decimal")
}
+ fn money(amount: Decimal, currency: Currency) -> Money {
+ Money::try_new(amount, currency).unwrap()
+ }
+
+ fn quantity(amount: Decimal, unit: Unit) -> Quantity {
+ Quantity::try_new(amount, unit).unwrap()
+ }
+
+ fn price(amount: Money, quantity: Quantity) -> QuantityPrice {
+ QuantityPrice::try_new(amount, quantity).unwrap()
+ }
+
fn d_tag(raw: &str) -> RadrootsDTag {
raw.parse().unwrap()
}
@@ -506,15 +518,15 @@ mod tests {
fn base_bin() -> RadrootsOperationalListingBin {
RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(decimal("1000"), Unit::MassG).with_label("bag"),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(decimal("0.01"), Currency::USD),
- Quantity::new(Decimal::ONE, Unit::MassG),
+ quantity: quantity(decimal("1000"), Unit::MassG).with_label("bag"),
+ price_per_canonical_unit: price(
+ money(decimal("0.01"), Currency::USD),
+ quantity(Decimal::ONE, Unit::MassG),
),
display_amount: Some(decimal("1")),
display_unit: Some(Unit::MassKg),
display_label: Some("kilobag".to_string()),
- display_price: Some(Money::new(decimal("10"), Currency::USD)),
+ display_price: Some(money(decimal("10"), Currency::USD)),
display_price_unit: Some(Unit::MassKg),
}
}
@@ -695,14 +707,15 @@ mod tests {
#[test]
fn discount_payload_without_serde_json_errors() {
- let discount = Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
+ let discount = Discount::try_new(
+ DiscountScope::Bin,
+ DiscountThreshold::BinCount {
bin_id: "bin-1".to_string(),
min: 2,
},
- value: DiscountValue::MoneyPerBin(Money::new(decimal("1"), Currency::USD)),
- };
+ DiscountValue::MoneyPerBin(money(decimal("1"), Currency::USD)),
+ )
+ .unwrap();
#[cfg(feature = "serde_json")]
{
let payload = discount_tag_payload(&discount).expect("serde_json payload");
@@ -791,7 +804,7 @@ mod tests {
assert_eq!(generic_tag[0], "price");
let mut non_canonical = base_bin();
- non_canonical.quantity = Quantity::new(decimal("1"), Unit::MassKg);
+ non_canonical.quantity = quantity(decimal("1"), Unit::MassKg);
let err = tag_listing_bin(&non_canonical).expect_err("non canonical quantity");
assert!(matches!(
err,
@@ -822,9 +835,9 @@ mod tests {
assert_eq!(no_display_tag.len(), 4);
let mut invalid_unit_price = base_bin();
- invalid_unit_price.price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(decimal("2"), Unit::MassG),
+ invalid_unit_price.price_per_canonical_unit = price(
+ money(decimal("10"), Currency::USD),
+ quantity(decimal("2"), Unit::MassG),
);
let err = tag_listing_price(&invalid_unit_price).expect_err("not unit price");
assert!(matches!(
@@ -833,7 +846,7 @@ mod tests {
));
let mut mismatch_display_currency = base_bin();
- mismatch_display_currency.display_price = Some(Money::new(decimal("10"), Currency::EUR));
+ mismatch_display_currency.display_price = Some(money(decimal("10"), Currency::EUR));
let err = tag_listing_price(&mismatch_display_currency).expect_err("currency mismatch");
assert!(matches!(
err,
@@ -865,9 +878,9 @@ mod tests {
assert_eq!(tag.len(), 6);
let mut invalid_cost = base_bin();
- invalid_cost.price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(Decimal::ONE, Unit::Each),
+ invalid_cost.price_per_canonical_unit = price(
+ money(decimal("10"), Currency::USD),
+ quantity(Decimal::ONE, Unit::Each),
);
let err = bin_total_price(&invalid_cost).expect_err("invalid cost conversion");
assert!(matches!(
@@ -880,9 +893,9 @@ mod tests {
fn operational_listing_tags_propagate_bin_and_resource_area_validation_errors() {
let mut listing = base_listing();
listing.discounts = None;
- listing.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(decimal("2"), Unit::MassG),
+ listing.bins[0].price_per_canonical_unit = price(
+ money(decimal("10"), Currency::USD),
+ quantity(decimal("2"), Unit::MassG),
);
let err = operational_listing_tags_with_options(
&listing,
@@ -896,10 +909,10 @@ mod tests {
let mut listing = base_listing();
listing.discounts = None;
- listing.bins[0].quantity = Quantity::new(decimal("1"), Unit::MassKg);
- listing.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(Decimal::ONE, Unit::MassG),
+ listing.bins[0].quantity = quantity(decimal("1"), Unit::MassKg);
+ listing.bins[0].price_per_canonical_unit = price(
+ money(decimal("10"), Currency::USD),
+ quantity(Decimal::ONE, Unit::MassG),
);
let err = operational_listing_tags_with_options(
&listing,
@@ -913,10 +926,10 @@ mod tests {
let mut listing = base_listing();
listing.discounts = None;
- listing.bins[0].quantity = Quantity::new(Decimal::ONE, Unit::Each);
- listing.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(Decimal::ONE, Unit::MassG),
+ listing.bins[0].quantity = quantity(Decimal::ONE, Unit::Each);
+ listing.bins[0].price_per_canonical_unit = price(
+ money(decimal("10"), Currency::USD),
+ quantity(Decimal::ONE, Unit::MassG),
);
let err = operational_listing_tags_with_options(
&listing,
@@ -948,14 +961,17 @@ mod tests {
#[test]
fn operational_listing_tags_required_errors_and_success_paths() {
let mut listing_with_discount = base_listing();
- listing_with_discount.discounts = Some(vec![Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
- bin_id: "bin-1".to_string(),
- min: 2,
- },
- value: DiscountValue::MoneyPerBin(Money::new(decimal("1"), Currency::USD)),
- }]);
+ listing_with_discount.discounts = Some(vec![
+ Discount::try_new(
+ DiscountScope::Bin,
+ DiscountThreshold::BinCount {
+ bin_id: "bin-1".to_string(),
+ min: 2,
+ },
+ DiscountValue::MoneyPerBin(money(decimal("1"), Currency::USD)),
+ )
+ .unwrap(),
+ ]);
#[cfg(feature = "serde_json")]
{
let tags = operational_listing_tags_with_options(
@@ -1280,7 +1296,7 @@ mod tests {
let mut first = base_bin();
first.bin_id = bin_id("bin-2");
first.display_label = None;
- first.quantity = Quantity::new(decimal("1000"), Unit::MassG);
+ first.quantity = quantity(decimal("1000"), Unit::MassG);
let mut second = base_bin();
second.bin_id = bin_id("bin-1");
listing_without_primary_match.primary_bin_id = bin_id("bin-missing");
diff --git a/crates/event_codec/src/order/decode.rs b/crates/event_codec/src/order/decode.rs
@@ -465,7 +465,7 @@ mod tests {
}
fn usd(raw: &str) -> Money {
- Money::new(decimal(raw), Currency::USD)
+ Money::try_new(decimal(raw), Currency::USD).unwrap()
}
fn request_economics() -> RadrootsOrderEconomics {
diff --git a/crates/event_codec/src/resource_cap/encode.rs b/crates/event_codec/src/resource_cap/encode.rs
@@ -123,7 +123,7 @@ mod tests {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/src/resource_cap/mod.rs b/crates/event_codec/src/resource_cap/mod.rs
@@ -27,7 +27,7 @@ mod tests {
},
start: 1735689600,
end: 1767225600,
- cap_quantity: Quantity::new(Decimal::from(100000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(100000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -71,7 +71,7 @@ mod tests {
},
start: 1735689600,
end: 1767225600,
- cap_quantity: Quantity::new(Decimal::from(100000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(100000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/tests/domain_encode_non_serde.rs b/crates/event_codec/tests/domain_encode_non_serde.rs
@@ -55,6 +55,18 @@ fn decimal(value: &str) -> Decimal {
Decimal::from_str(value).expect("valid decimal")
}
+fn money(amount: Decimal, currency: Currency) -> Money {
+ Money::try_new(amount, currency).unwrap()
+}
+
+fn quantity(amount: Decimal, unit: Unit) -> Quantity {
+ Quantity::try_new(amount, unit).unwrap()
+}
+
+fn quantity_price(amount: Money, quantity: Quantity) -> QuantityPrice {
+ QuantityPrice::try_new(amount, quantity).unwrap()
+}
+
fn listing_d_tag(raw: &str) -> RadrootsDTag {
raw.parse().unwrap()
}
@@ -157,11 +169,9 @@ fn sample_plot() -> RadrootsPlot {
}
fn sample_listing() -> RadrootsOperationalListing {
- let quantity = Quantity::new(Decimal::from(1u32), Unit::Each);
- let price_per_canonical_unit = QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
- quantity.clone(),
- );
+ let quantity = quantity(Decimal::from(1u32), Unit::Each);
+ let price_per_canonical_unit =
+ quantity_price(money(Decimal::from(10u32), Currency::USD), quantity.clone());
RadrootsOperationalListing {
d_tag: listing_d_tag(VALID_DOC_D_TAG),
@@ -235,7 +245,7 @@ fn sample_resource_cap() -> RadrootsResourceHarvestCap {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(decimal("1000"), Unit::MassG),
+ cap_quantity: quantity(decimal("1000"), Unit::MassG),
display_amount: None,
display_unit: None,
display_label: None,
@@ -868,7 +878,7 @@ fn listing_encode_paths() {
));
let mut invalid = sample_listing();
- invalid.bins[0].display_price = Some(Money::new(decimal("10"), Currency::USD));
+ invalid.bins[0].display_price = Some(money(decimal("10"), Currency::USD));
invalid.bins[0].display_price_unit = None;
let err =
operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
@@ -879,8 +889,7 @@ fn listing_encode_paths() {
));
let mut listing_with_display_price = sample_listing();
- listing_with_display_price.bins[0].display_price =
- Some(Money::new(decimal("10"), Currency::USD));
+ listing_with_display_price.bins[0].display_price = Some(money(decimal("10"), Currency::USD));
listing_with_display_price.bins[0].display_price_unit = Some(Unit::Each);
let display_price_tags = operational_listing_tags_with_options(
&listing_with_display_price,
@@ -894,7 +903,7 @@ fn listing_encode_paths() {
}));
let mut invalid = listing_with_display_price.clone();
- invalid.bins[0].display_price = Some(Money::new(decimal("10"), Currency::EUR));
+ invalid.bins[0].display_price = Some(money(decimal("10"), Currency::EUR));
let err =
operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
.expect_err("display price currency mismatch");
@@ -915,10 +924,10 @@ fn listing_encode_paths() {
));
let mut invalid = sample_listing();
- invalid.bins[0].quantity = Quantity::new(decimal("1"), Unit::MassKg);
- invalid.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(decimal("1"), Unit::MassG),
+ invalid.bins[0].quantity = quantity(decimal("1"), Unit::MassKg);
+ invalid.bins[0].price_per_canonical_unit = quantity_price(
+ money(decimal("10"), Currency::USD),
+ quantity(decimal("1"), Unit::MassG),
);
let err =
operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
@@ -929,9 +938,9 @@ fn listing_encode_paths() {
));
let mut invalid = sample_listing();
- invalid.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(decimal("2"), Unit::Each),
+ invalid.bins[0].price_per_canonical_unit = quantity_price(
+ money(decimal("10"), Currency::USD),
+ quantity(decimal("2"), Unit::Each),
);
let err =
operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
@@ -942,9 +951,9 @@ fn listing_encode_paths() {
));
let mut invalid = sample_listing();
- invalid.bins[0].price_per_canonical_unit = QuantityPrice::new(
- Money::new(decimal("10"), Currency::USD),
- Quantity::new(decimal("1"), Unit::MassG),
+ invalid.bins[0].price_per_canonical_unit = quantity_price(
+ money(decimal("10"), Currency::USD),
+ quantity(decimal("1"), Unit::MassG),
);
let err =
operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
@@ -1290,6 +1299,6 @@ fn document_encode_paths() {
#[test]
fn resource_harvest_cap_money_type_sanity() {
- let money = Money::new(decimal("1"), Currency::USD);
- assert_eq!(money.amount.to_string(), "1");
+ let money = money(decimal("1"), Currency::USD);
+ assert_eq!(money.amount().to_string(), "1");
}
diff --git a/crates/event_codec/tests/operational_listing.rs b/crates/event_codec/tests/operational_listing.rs
@@ -56,6 +56,18 @@ fn bin_id(raw: &str) -> RadrootsInventoryBinId {
raw.parse().unwrap()
}
+fn money(amount: Decimal, currency: Currency) -> Money {
+ Money::try_new(amount, currency).unwrap()
+}
+
+fn quantity(amount: Decimal, unit: Unit) -> Quantity {
+ Quantity::try_new(amount, unit).unwrap()
+}
+
+fn quantity_price(amount: Money, quantity: Quantity) -> QuantityPrice {
+ QuantityPrice::try_new(amount, quantity).unwrap()
+}
+
fn sample_operational_listing_tags() -> Vec<Vec<String>> {
operational_listing_build_tags(&sample_listing("AAAAAAAAAAAAAAAAAAAAAg")).unwrap()
}
@@ -100,11 +112,8 @@ fn assert_invalid_tag(tags: Vec<Vec<String>>, expected: &'static str) {
}
fn sample_listing(d_tag: &str) -> RadrootsOperationalListing {
- let quantity = Quantity::new(Decimal::from(1u32), Unit::Each);
- let price = QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
- quantity.clone(),
- );
+ let quantity = quantity(Decimal::from(1u32), Unit::Each);
+ let price = quantity_price(money(Decimal::from(10u32), Currency::USD), quantity.clone());
RadrootsOperationalListing {
d_tag: listing_d_tag(d_tag),
@@ -219,7 +228,7 @@ fn operational_listing_from_event_parts_does_not_allow_json_content_to_override_
listing.product.title = "Content override".to_string();
listing.product.category = "Content category".to_string();
listing.inventory_available = Some(Decimal::from(99u32));
- listing.bins[0].quantity.amount = Decimal::from(42u32);
+ listing.bins[0].quantity = quantity(Decimal::from(42u32), Unit::Each);
let decoded =
operational_listing_from_event_parts(&tags, &serde_json::to_string(&listing).unwrap())
@@ -228,7 +237,7 @@ fn operational_listing_from_event_parts_does_not_allow_json_content_to_override_
assert_eq!(decoded.product.title, "Widget");
assert_eq!(decoded.product.category, "Tools");
assert_eq!(decoded.inventory_available, None);
- assert_eq!(decoded.bins[0].quantity.amount, Decimal::from(1u32));
+ assert_eq!(decoded.bins[0].quantity.amount(), Decimal::from(1u32));
}
#[test]
@@ -299,22 +308,30 @@ fn execute_listing_parse_vector(vector: &Value, id: &str) {
.find(|bin| bin.bin_id == listing.primary_bin_id)
.expect("primary listing bin");
assert_eq!(
- primary.quantity.amount.to_string(),
+ primary.quantity.amount().to_string(),
expected["quantity_amount"],
"{id}"
);
assert_eq!(
- primary.quantity.unit.code(),
+ primary.quantity.unit().code(),
expected["quantity_unit"],
"{id}"
);
assert_eq!(
- primary.price_per_canonical_unit.amount.amount.to_string(),
+ primary
+ .price_per_canonical_unit
+ .amount()
+ .amount()
+ .to_string(),
expected["price_amount"],
"{id}"
);
assert_eq!(
- primary.price_per_canonical_unit.amount.currency.as_str(),
+ primary
+ .price_per_canonical_unit
+ .amount()
+ .currency()
+ .as_str(),
expected["price_currency"],
"{id}"
);
@@ -370,30 +387,30 @@ fn sample_listing_full(d_tag: &str) -> RadrootsOperationalListing {
primary_bin_id: bin_id("bin-1"),
bins: vec![RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(qty_amount, Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(price_amount, Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::MassG),
+ quantity: quantity(qty_amount, Unit::MassG),
+ price_per_canonical_unit: quantity_price(
+ money(price_amount, Currency::USD),
+ quantity(Decimal::from(1u32), Unit::MassG),
),
display_amount: Some(display_qty),
display_unit: Some(Unit::MassKg),
display_label: Some("bag".to_string()),
- display_price: Some(Money::new(display_price, Currency::USD)),
+ display_price: Some(money(display_price, Currency::USD)),
display_price_unit: Some(Unit::MassKg),
}],
resource_area: None,
plot: None,
- discounts: Some(vec![Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
- bin_id: "bin-1".to_string(),
- min: 5,
- },
- value: DiscountValue::MoneyPerBin(Money::new(
- Decimal::from_str("2").unwrap(),
- Currency::USD,
- )),
- }]),
+ discounts: Some(vec![
+ Discount::try_new(
+ DiscountScope::Bin,
+ DiscountThreshold::BinCount {
+ bin_id: "bin-1".to_string(),
+ min: 5,
+ },
+ DiscountValue::MoneyPerBin(money(Decimal::from_str("2").unwrap(), Currency::USD)),
+ )
+ .unwrap(),
+ ]),
inventory_available: None,
availability: None,
delivery_method: None,
@@ -1312,15 +1329,15 @@ fn operational_listing_tags_full_uses_single_generic_price_for_primary_bin() {
let mut listing = sample_listing_full("AAAAAAAAAAAAAAAAAAAAAw");
listing.bins.push(RadrootsOperationalListingBin {
bin_id: bin_id("bin-2"),
- quantity: Quantity::new(Decimal::from_str("500").unwrap(), Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(Decimal::from_str("0.02").unwrap(), Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::MassG),
+ quantity: quantity(Decimal::from_str("500").unwrap(), Unit::MassG),
+ price_per_canonical_unit: quantity_price(
+ money(Decimal::from_str("0.02").unwrap(), Currency::USD),
+ quantity(Decimal::from(1u32), Unit::MassG),
),
display_amount: Some(Decimal::from(500u32)),
display_unit: Some(Unit::MassG),
display_label: Some("sample".to_string()),
- display_price: Some(Money::new(Decimal::from_str("10").unwrap(), Currency::USD)),
+ display_price: Some(money(Decimal::from_str("10").unwrap(), Currency::USD)),
display_price_unit: Some(Unit::MassG),
});
diff --git a/crates/event_codec/tests/structured_decode.rs b/crates/event_codec/tests/structured_decode.rs
@@ -176,7 +176,7 @@ fn sample_resource_cap(d_tag: &str) -> RadrootsResourceHarvestCap {
},
start: 100,
end: 200,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/tests/structured_encode_default.rs b/crates/event_codec/tests/structured_encode_default.rs
@@ -107,11 +107,12 @@ fn sample_gcs() -> RadrootsGcsLocation {
}
fn sample_listing(d_tag: &str) -> RadrootsOperationalListing {
- let quantity = Quantity::new(Decimal::from(1u32), Unit::Each);
- let price = QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
+ let quantity = Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap();
+ let price = QuantityPrice::try_new(
+ Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(),
quantity.clone(),
- );
+ )
+ .unwrap();
RadrootsOperationalListing {
d_tag: listing_d_tag(d_tag),
published_at: None,
@@ -256,7 +257,7 @@ fn sample_resource_cap() -> RadrootsResourceHarvestCap {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -564,7 +565,7 @@ fn structured_build_tags_cover_optional_and_error_paths() {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -821,7 +822,7 @@ fn structured_build_tags_cover_required_field_errors() {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/event_codec/tests/tag_builders.rs b/crates/event_codec/tests/tag_builders.rs
@@ -109,11 +109,12 @@ fn sample_gcs() -> RadrootsGcsLocation {
}
fn sample_listing() -> RadrootsOperationalListing {
- let quantity = Quantity::new(Decimal::from(1u32), Unit::Each);
- let price = QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
+ let quantity = Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap();
+ let price = QuantityPrice::try_new(
+ Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(),
quantity.clone(),
- );
+ )
+ .unwrap();
RadrootsOperationalListing {
d_tag: d_tag("AAAAAAAAAAAAAAAAAAAAAg"),
@@ -268,7 +269,7 @@ fn event_tag_builder_impls_build_tags_for_all_supported_types() {
},
start: 1,
end: 2,
- cap_quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
+ cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -564,14 +565,17 @@ fn listing_and_message_builders_cover_optional_shapes() {
));
let mut listing_with_discount_payload = listing_with_trade.clone();
- listing_with_discount_payload.discounts = Some(vec![Discount {
- scope: DiscountScope::Bin,
- threshold: DiscountThreshold::BinCount {
- bin_id: "bin-1".to_string(),
- min: 2,
- },
- value: DiscountValue::MoneyPerBin(Money::new(Decimal::from(1u32), Currency::USD)),
- }]);
+ listing_with_discount_payload.discounts = Some(vec![
+ Discount::try_new(
+ DiscountScope::Bin,
+ DiscountThreshold::BinCount {
+ bin_id: "bin-1".to_string(),
+ min: 2,
+ },
+ DiscountValue::MoneyPerBin(Money::try_new(Decimal::from(1u32), Currency::USD).unwrap()),
+ )
+ .unwrap(),
+ ]);
#[cfg(feature = "serde_json")]
{
let tags =
diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json
@@ -159,50 +159,50 @@
{
"role": "core_currency_value_authority",
"path": "crates/core/src/currency.rs",
- "byte_length": 4390,
- "sha256": "14b73f219a93ae5b70f1c63c93e0bd1a587c0e95c10e7978e855c6a3da204b5c",
+ "byte_length": 4142,
+ "sha256": "42e6da2d8d2fdd6955dc2d83e98dd00266e02bc910b69923b117662c05089d14",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_decimal_value_authority",
"path": "crates/core/src/decimal.rs",
- "byte_length": 8480,
- "sha256": "9b5bdc52fd13bffb3bd5fd2954c4c37fddc86be1bb7c754bef35bd9442a92381",
+ "byte_length": 7839,
+ "sha256": "4b1d681a92d7a9e074bee7e1f20eb1499bb05bfc6528a6c9ba69fa7d00416550",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_money_value_authority",
"path": "crates/core/src/money.rs",
- "byte_length": 9214,
- "sha256": "3377f0ca92f1e66cf6d8cecb88cfd79304113873ef56757eb93cd3e63c2a4eb2",
+ "byte_length": 8025,
+ "sha256": "3f30bc21e21951a62fdc5d4033736ed42a883df590e8403940775bd2edfeebc5",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_percent_value_authority",
"path": "crates/core/src/percent.rs",
- "byte_length": 3341,
- "sha256": "9c0a87ae035e2f574b1a6ea2844e924c70ce19645becda99508c680b7c4c846f",
+ "byte_length": 2652,
+ "sha256": "42ceab109881329f3539d06a0ee0381ab4359985fe185a18d58a9c9558c5eb7f",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_quantity_value_authority",
"path": "crates/core/src/quantity.rs",
- "byte_length": 8108,
- "sha256": "18237c5b905395dded9682418e05be1844623eef64943dc844fb28b0f4036c97",
+ "byte_length": 6785,
+ "sha256": "d14e619da3829cdaf3ca7385fb892d330c286c1e21455c135c37518cbb588b5f",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_quantity_price_value_authority",
"path": "crates/core/src/quantity_price.rs",
- "byte_length": 8988,
- "sha256": "0b6fa25c5ad4c7d809bd81cdc2c054f5c4ad9d7963fe0824843df5ce9acddba7",
+ "byte_length": 7061,
+ "sha256": "d60f70377099470ed283f45868a5119d7bb46143cb250b5082a4c038ba7f6cdc",
"hash_algorithm": "sha256_bytes_v1"
},
{
"role": "core_unit_value_authority",
"path": "crates/core/src/unit.rs",
- "byte_length": 10512,
- "sha256": "8228766a56d7c12df8a631a4d2878763a6d0095088b409bfd82bc4a9f89608bc",
+ "byte_length": 10023,
+ "sha256": "550e9582b6a5290aac3f637b83853b21fefcbab92c432d067c83448f4b2fe3e9",
"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 @@
-61d798a43eed84a67c7d37926cfa58b60ad70194910a6bdeef86d88ac0e9bcb1
+e739b4f5386d971cb4a2cca198015e7ae68b2e36e11ec75113eb0e18b5bb0d81
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\": 11506,\n \"sha256\": \"af871d7567594f75da8385691297a5ce170f51ae54c4c318db10b99d458a3335\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_currency_value_authority\",\n \"path\": \"crates/core/src/currency.rs\",\n \"byte_length\": 4390,\n \"sha256\": \"14b73f219a93ae5b70f1c63c93e0bd1a587c0e95c10e7978e855c6a3da204b5c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_decimal_value_authority\",\n \"path\": \"crates/core/src/decimal.rs\",\n \"byte_length\": 8480,\n \"sha256\": \"9b5bdc52fd13bffb3bd5fd2954c4c37fddc86be1bb7c754bef35bd9442a92381\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_money_value_authority\",\n \"path\": \"crates/core/src/money.rs\",\n \"byte_length\": 9214,\n \"sha256\": \"3377f0ca92f1e66cf6d8cecb88cfd79304113873ef56757eb93cd3e63c2a4eb2\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_percent_value_authority\",\n \"path\": \"crates/core/src/percent.rs\",\n \"byte_length\": 3341,\n \"sha256\": \"9c0a87ae035e2f574b1a6ea2844e924c70ce19645becda99508c680b7c4c846f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_value_authority\",\n \"path\": \"crates/core/src/quantity.rs\",\n \"byte_length\": 8108,\n \"sha256\": \"18237c5b905395dded9682418e05be1844623eef64943dc844fb28b0f4036c97\",\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\": 8988,\n \"sha256\": \"0b6fa25c5ad4c7d809bd81cdc2c054f5c4ad9d7963fe0824843df5ce9acddba7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_unit_value_authority\",\n \"path\": \"crates/core/src/unit.rs\",\n \"byte_length\": 10512,\n \"sha256\": \"8228766a56d7c12df8a631a4d2878763a6d0095088b409bfd82bc4a9f89608bc\",\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\": 835558,\n \"sha256\": \"51a01360e2c32c83056cb36e22b850e6b09d2ad4e4a749097564b4a100709a16\",\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_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\": 11506,\n \"sha256\": \"af871d7567594f75da8385691297a5ce170f51ae54c4c318db10b99d458a3335\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_currency_value_authority\",\n \"path\": \"crates/core/src/currency.rs\",\n \"byte_length\": 4142,\n \"sha256\": \"42e6da2d8d2fdd6955dc2d83e98dd00266e02bc910b69923b117662c05089d14\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_decimal_value_authority\",\n \"path\": \"crates/core/src/decimal.rs\",\n \"byte_length\": 7839,\n \"sha256\": \"4b1d681a92d7a9e074bee7e1f20eb1499bb05bfc6528a6c9ba69fa7d00416550\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_money_value_authority\",\n \"path\": \"crates/core/src/money.rs\",\n \"byte_length\": 8025,\n \"sha256\": \"3f30bc21e21951a62fdc5d4033736ed42a883df590e8403940775bd2edfeebc5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_percent_value_authority\",\n \"path\": \"crates/core/src/percent.rs\",\n \"byte_length\": 2652,\n \"sha256\": \"42ceab109881329f3539d06a0ee0381ab4359985fe185a18d58a9c9558c5eb7f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_quantity_value_authority\",\n \"path\": \"crates/core/src/quantity.rs\",\n \"byte_length\": 6785,\n \"sha256\": \"d14e619da3829cdaf3ca7385fb892d330c286c1e21455c135c37518cbb588b5f\",\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\": 7061,\n \"sha256\": \"d60f70377099470ed283f45868a5119d7bb46143cb250b5082a4c038ba7f6cdc\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"core_unit_value_authority\",\n \"path\": \"crates/core/src/unit.rs\",\n \"byte_length\": 10023,\n \"sha256\": \"550e9582b6a5290aac3f637b83853b21fefcbab92c432d067c83448f4b2fe3e9\",\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\": 835558,\n \"sha256\": \"51a01360e2c32c83056cb36e22b850e6b09d2ad4e4a749097564b4a100709a16\",\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 = 16260;
pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str =
- "61d798a43eed84a67c7d37926cfa58b60ad70194910a6bdeef86d88ac0e9bcb1";
+ "e739b4f5386d971cb4a2cca198015e7ae68b2e36e11ec75113eb0e18b5bb0d81";
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/crates/replica_sync/src/ingest.rs b/crates/replica_sync/src/ingest.rs
@@ -2147,11 +2147,14 @@ mod tests {
primary_bin_id: "bin-a".parse().expect("primary bin id"),
bins: vec![RadrootsOperationalListingBin {
bin_id: "bin-a".parse().expect("bin id"),
- quantity: Quantity::new(listing_decimal("12"), Unit::Each).with_label("unit label"),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(listing_decimal("6"), listing_currency()),
- Quantity::new(listing_decimal("1"), Unit::Each),
- ),
+ quantity: Quantity::try_new(listing_decimal("12"), Unit::Each)
+ .unwrap()
+ .with_label("unit label"),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(listing_decimal("6"), listing_currency()).unwrap(),
+ Quantity::try_new(listing_decimal("1"), Unit::Each).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/trade/src/operational_listing/draft.rs b/crates/trade/src/operational_listing/draft.rs
@@ -250,11 +250,12 @@ mod tests {
primary_bin_id: bin_id("bin-1"),
bins: vec![RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice {
- amount: Money::new(Decimal::from(20u32), Currency::USD),
- quantity: Quantity::new(Decimal::from(1u32), Unit::MassG),
- },
+ quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(20u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -529,7 +530,7 @@ mod tests {
let mut invalid_quantity_listing = listing();
let mut secondary_bin = invalid_quantity_listing.bins[0].clone();
secondary_bin.bin_id = bin_id("bin-2");
- secondary_bin.quantity.amount = "-1".parse().expect("negative decimal");
+ secondary_bin.quantity = Quantity::try_new(Decimal::ONE, Unit::MassKg).unwrap();
invalid_quantity_listing.bins.push(secondary_bin);
let error = RadrootsOperationalListingCanonicalEdit::new(
@@ -548,7 +549,11 @@ mod tests {
let mut mismatched_unit_listing = listing();
let mut secondary_bin = mismatched_unit_listing.bins[0].clone();
secondary_bin.bin_id = bin_id("bin-2");
- secondary_bin.price_per_canonical_unit.quantity.unit = Unit::Each;
+ secondary_bin.price_per_canonical_unit = QuantityPrice::try_new(
+ secondary_bin.price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::ONE, Unit::Each).unwrap(),
+ )
+ .unwrap();
mismatched_unit_listing.bins.push(secondary_bin);
let error = RadrootsOperationalListingCanonicalEdit::new(
diff --git a/crates/trade/src/operational_listing/mutation.rs b/crates/trade/src/operational_listing/mutation.rs
@@ -233,11 +233,12 @@ mod tests {
primary_bin_id: bin_id("bin-1"),
bins: vec![RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice {
- amount: Money::new(Decimal::from(20u32), Currency::USD),
- quantity: Quantity::new(Decimal::from(1u32), Unit::MassG),
- },
+ quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(20u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/trade/src/operational_listing/price_ext.rs b/crates/trade/src/operational_listing/price_ext.rs
@@ -5,11 +5,6 @@ use radroots_core::pricing::{Error as PricingError, QuantityPriceOps};
use radroots_core::{Decimal, Quantity};
use radroots_event::operational_listing::RadrootsOperationalListingBin;
-pub trait BinPricingExt {
- fn subtotal_for_count(&self, bin_count: u32) -> RadrootsOperationalListingSubtotal;
- fn total_for_count(&self, bin_count: u32) -> RadrootsOperationalListingTotal;
-}
-
pub trait BinPricingTryExt {
fn try_subtotal_for_count(
&self,
@@ -22,36 +17,16 @@ pub trait BinPricingTryExt {
}
#[inline]
-fn effective_quantity(bin: &RadrootsOperationalListingBin, bin_count: u32) -> Quantity {
- let amount = bin.quantity.amount * Decimal::from(bin_count);
- Quantity::new(amount, bin.quantity.unit)
-}
-
-impl BinPricingExt for RadrootsOperationalListingBin {
- fn subtotal_for_count(&self, bin_count: u32) -> RadrootsOperationalListingSubtotal {
- let effective_qty = effective_quantity(self, bin_count);
- let money = self
- .price_per_canonical_unit
- .cost_for_rounded(&effective_qty);
- let currency = money.currency;
-
- RadrootsOperationalListingSubtotal {
- price_amount: money,
- price_currency: currency,
- quantity_amount: effective_qty.amount,
- quantity_unit: effective_qty.unit,
- }
- }
-
- fn total_for_count(&self, bin_count: u32) -> RadrootsOperationalListingTotal {
- let sub = self.subtotal_for_count(bin_count);
- RadrootsOperationalListingTotal {
- price_amount: sub.price_amount,
- price_currency: sub.price_currency,
- quantity_amount: sub.quantity_amount,
- quantity_unit: sub.quantity_unit,
- }
- }
+fn effective_quantity(
+ bin: &RadrootsOperationalListingBin,
+ bin_count: u32,
+) -> Result<Quantity, PricingError> {
+ let amount = bin
+ .quantity
+ .amount()
+ .checked_mul(Decimal::from(bin_count))
+ .map_err(|_| PricingError::ArithmeticOverflow)?;
+ Quantity::try_new(amount, bin.quantity.unit()).map_err(|_| PricingError::ArithmeticOverflow)
}
impl BinPricingTryExt for RadrootsOperationalListingBin {
@@ -59,17 +34,17 @@ impl BinPricingTryExt for RadrootsOperationalListingBin {
&self,
bin_count: u32,
) -> Result<RadrootsOperationalListingSubtotal, PricingError> {
- let effective_qty = effective_quantity(self, bin_count);
+ let effective_qty = effective_quantity(self, bin_count)?;
let money = self
.price_per_canonical_unit
.try_cost_for_rounded(&effective_qty)?;
- let currency = money.currency;
+ let currency = money.currency();
Ok(RadrootsOperationalListingSubtotal {
price_amount: money,
price_currency: currency,
- quantity_amount: effective_qty.amount,
- quantity_unit: effective_qty.unit,
+ quantity_amount: effective_qty.amount(),
+ quantity_unit: effective_qty.unit(),
})
}
@@ -89,7 +64,7 @@ impl BinPricingTryExt for RadrootsOperationalListingBin {
#[cfg(test)]
mod tests {
- use super::{BinPricingExt, BinPricingTryExt};
+ use super::BinPricingTryExt;
use radroots_core::pricing::Error as PricingError;
use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
use radroots_event::ids::RadrootsInventoryBinId;
@@ -102,11 +77,12 @@ mod tests {
fn valid_bin() -> RadrootsOperationalListingBin {
RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(2u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(Decimal::from(5u32), Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::MassG),
- ),
+ quantity: Quantity::try_new(Decimal::from(2u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(5u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -119,11 +95,12 @@ mod tests {
fn try_subtotal_for_rejects_unit_mismatch() {
let bin = RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::Each),
- ),
+ quantity: Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -144,12 +121,12 @@ mod tests {
#[test]
fn subtotal_and_total_for_count_follow_effective_quantity() {
let bin = valid_bin();
- let subtotal = bin.subtotal_for_count(3);
- let total = bin.total_for_count(3);
+ let subtotal = bin.try_subtotal_for_count(3).unwrap();
+ let total = bin.try_total_for_count(3).unwrap();
assert_eq!(subtotal.quantity_amount, Decimal::from(6u32));
assert_eq!(subtotal.quantity_unit, Unit::MassG);
- assert_eq!(subtotal.price_amount.amount, Decimal::from(30u32));
+ assert_eq!(subtotal.price_amount.amount(), Decimal::from(30u32));
assert_eq!(subtotal.price_currency, Currency::USD);
assert_eq!(total.quantity_amount, subtotal.quantity_amount);
@@ -159,13 +136,13 @@ mod tests {
}
#[test]
- fn try_subtotal_and_try_total_match_non_fallible_paths() {
+ fn try_subtotal_and_try_total_match() {
let bin = valid_bin();
let subtotal = bin.try_subtotal_for_count(4).expect("subtotal");
let total = bin.try_total_for_count(4).expect("total");
assert_eq!(subtotal.quantity_amount, Decimal::from(8u32));
- assert_eq!(subtotal.price_amount.amount, Decimal::from(40u32));
+ assert_eq!(subtotal.price_amount.amount(), Decimal::from(40u32));
assert_eq!(total.quantity_amount, subtotal.quantity_amount);
assert_eq!(total.price_amount, subtotal.price_amount);
}
@@ -174,11 +151,12 @@ mod tests {
fn try_total_for_count_propagates_subtotal_errors() {
let bin = RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice::new(
- Money::new(Decimal::from(10u32), Currency::USD),
- Quantity::new(Decimal::from(1u32), Unit::Each),
- ),
+ quantity: Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
diff --git a/crates/trade/src/operational_listing/validation.rs b/crates/trade/src/operational_listing/validation.rs
@@ -188,8 +188,8 @@ pub fn validate_operational_listing_model(
product_type,
primary_bin_id: primary_bin_id.clone(),
bin_quantity: primary_bin.quantity.clone(),
- unit: primary_bin.quantity.unit,
- unit_price: primary_bin.price_per_canonical_unit.amount.clone(),
+ unit: primary_bin.quantity.unit(),
+ unit_price: primary_bin.price_per_canonical_unit.amount().clone(),
inventory_available,
availability,
location,
@@ -201,16 +201,16 @@ pub fn validate_operational_listing_model(
fn validate_listing_bin(
bin: &RadrootsOperationalListingBin,
) -> Result<(), OperationalListingValidationError> {
- if bin.quantity.amount.is_sign_negative() || !bin.quantity.is_canonical() {
+ if bin.quantity.amount().is_sign_negative() || !bin.quantity.is_canonical() {
return Err(OperationalListingValidationError::InvalidBin);
}
if !bin.price_per_canonical_unit.is_price_per_canonical_unit()
|| bin
.price_per_canonical_unit
- .amount
- .amount
+ .amount()
+ .amount()
.is_sign_negative()
- || bin.price_per_canonical_unit.quantity.unit != bin.quantity.unit
+ || bin.price_per_canonical_unit.quantity().unit() != bin.quantity.unit()
{
return Err(OperationalListingValidationError::InvalidPrice);
}
@@ -297,11 +297,12 @@ mod tests {
primary_bin_id: bin_id("bin-1"),
bins: vec![RadrootsOperationalListingBin {
bin_id: bin_id("bin-1"),
- quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG),
- price_per_canonical_unit: QuantityPrice {
- amount: Money::new(Decimal::from(20u32), Currency::USD),
- quantity: Quantity::new(Decimal::from(1u32), Unit::MassG),
- },
+ quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(),
+ price_per_canonical_unit: QuantityPrice::try_new(
+ Money::try_new(Decimal::from(20u32), Currency::USD).unwrap(),
+ Quantity::try_new(Decimal::from(1u32), Unit::MassG).unwrap(),
+ )
+ .unwrap(),
display_amount: None,
display_unit: None,
display_label: None,
@@ -352,22 +353,22 @@ mod tests {
tags.push(vec![
"radroots:bin".into(),
bin.bin_id.to_string(),
- bin.quantity.amount.to_string(),
- bin.quantity.unit.code().to_string(),
+ bin.quantity.amount().to_string(),
+ bin.quantity.unit().code().to_string(),
]);
tags.push(vec![
"radroots:price".into(),
bin.bin_id.to_string(),
- bin.price_per_canonical_unit.amount.amount.to_string(),
+ bin.price_per_canonical_unit.amount().amount().to_string(),
bin.price_per_canonical_unit
- .amount
- .currency
+ .amount()
+ .currency()
.as_str()
.to_string(),
- bin.price_per_canonical_unit.quantity.amount.to_string(),
+ bin.price_per_canonical_unit.quantity().amount().to_string(),
bin.price_per_canonical_unit
- .quantity
- .unit
+ .quantity()
+ .unit()
.code()
.to_string(),
]);
@@ -489,19 +490,6 @@ mod tests {
listing
}
- fn assert_model_and_signed_event_error(
- listing: RadrootsOperationalListing,
- expected: OperationalListingValidationError,
- ) {
- let event_error = validate_operational_listing_event(&base_event(&listing))
- .expect_err("signed event semantic error");
- let model_error = validate_operational_listing_model(listing, &seller_pubkey())
- .expect_err("model semantic error");
-
- assert_eq!(event_error, expected);
- assert_eq!(model_error, event_error);
- }
-
#[test]
fn validate_listing_ok() {
let listing = base_listing();
@@ -546,18 +534,6 @@ mod tests {
let model_error = validate_operational_listing_model(listing, &other_seller_pubkey())
.expect_err("model seller error");
assert_eq!(model_error, event_error);
-
- let mut listing = listing_with_secondary_bin();
- listing.bins[1].quantity.amount = "-1".parse().expect("negative decimal");
- assert_model_and_signed_event_error(listing, OperationalListingValidationError::InvalidBin);
-
- let mut listing = listing_with_secondary_bin();
- listing.bins[1].price_per_canonical_unit.amount.amount =
- "-1".parse().expect("negative decimal");
- assert_model_and_signed_event_error(
- listing,
- OperationalListingValidationError::InvalidPrice,
- );
}
#[test]
@@ -606,11 +582,7 @@ mod tests {
#[test]
fn model_validation_rejects_invalid_secondary_bin_quantities() {
assert_secondary_bin_model_error(
- |bin| bin.quantity.amount = "-1".parse().expect("negative decimal"),
- OperationalListingValidationError::InvalidBin,
- );
- assert_secondary_bin_model_error(
- |bin| bin.quantity.unit = Unit::MassKg,
+ |bin| bin.quantity = Quantity::try_new(Decimal::ONE, Unit::MassKg).unwrap(),
OperationalListingValidationError::InvalidBin,
);
}
@@ -619,26 +591,31 @@ mod tests {
fn model_validation_rejects_invalid_secondary_bin_prices() {
assert_secondary_bin_model_error(
|bin| {
- bin.price_per_canonical_unit.quantity.amount = Decimal::from(2u32);
+ bin.price_per_canonical_unit = QuantityPrice::try_new(
+ bin.price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::from(2u32), Unit::MassG).unwrap(),
+ )
+ .unwrap();
},
OperationalListingValidationError::InvalidPrice,
);
assert_secondary_bin_model_error(
|bin| {
- bin.price_per_canonical_unit.quantity.unit = Unit::MassKg;
+ bin.price_per_canonical_unit = QuantityPrice::try_new(
+ bin.price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::ONE, Unit::MassKg).unwrap(),
+ )
+ .unwrap();
},
OperationalListingValidationError::InvalidPrice,
);
assert_secondary_bin_model_error(
|bin| {
- bin.price_per_canonical_unit.amount.amount =
- "-1".parse().expect("negative decimal");
- },
- OperationalListingValidationError::InvalidPrice,
- );
- assert_secondary_bin_model_error(
- |bin| {
- bin.price_per_canonical_unit.quantity.unit = Unit::Each;
+ bin.price_per_canonical_unit = QuantityPrice::try_new(
+ bin.price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::ONE, Unit::Each).unwrap(),
+ )
+ .unwrap();
},
OperationalListingValidationError::InvalidPrice,
);
@@ -824,16 +801,9 @@ mod tests {
}
#[test]
- fn validate_listing_rejects_negative_quantity() {
- let mut listing = base_listing();
- listing.bins[0].quantity.amount = "-1".parse().unwrap();
- assert_validation_err(listing, OperationalListingValidationError::InvalidBin);
- }
-
- #[test]
fn validate_listing_rejects_non_canonical_quantity() {
let mut listing = base_listing();
- listing.bins[0].quantity.unit = Unit::MassKg;
+ listing.bins[0].quantity = Quantity::try_new(Decimal::ONE, Unit::MassKg).unwrap();
assert_validation_err(
listing,
OperationalListingValidationError::ParseError {
@@ -845,7 +815,11 @@ mod tests {
#[test]
fn validate_listing_rejects_non_canonical_price_quantity() {
let mut listing = base_listing();
- listing.bins[0].price_per_canonical_unit.quantity.unit = Unit::MassKg;
+ listing.bins[0].price_per_canonical_unit = QuantityPrice::try_new(
+ listing.bins[0].price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::ONE, Unit::MassKg).unwrap(),
+ )
+ .unwrap();
assert_validation_err(
listing,
OperationalListingValidationError::ParseError {
@@ -855,16 +829,13 @@ mod tests {
}
#[test]
- fn validate_listing_rejects_negative_price_amount() {
- let mut listing = base_listing();
- listing.bins[0].price_per_canonical_unit.amount.amount = "-1".parse().unwrap();
- assert_validation_err(listing, OperationalListingValidationError::InvalidPrice);
- }
-
- #[test]
fn validate_listing_rejects_price_unit_mismatch() {
let mut listing = base_listing();
- listing.bins[0].price_per_canonical_unit.quantity.unit = Unit::Each;
+ listing.bins[0].price_per_canonical_unit = QuantityPrice::try_new(
+ listing.bins[0].price_per_canonical_unit.amount().clone(),
+ Quantity::try_new(Decimal::ONE, Unit::Each).unwrap(),
+ )
+ .unwrap();
assert_validation_err(
listing,
OperationalListingValidationError::ParseError {
diff --git a/docs/api/radroots-core.txt b/docs/api/radroots-core.txt
@@ -7,13 +7,6 @@ pub fn radroots_core::Error::from(radroots_core::currency::ParseError) -> Self
impl core::error::Error for radroots_core::currency::ParseError
impl core::fmt::Display for radroots_core::currency::ParseError
pub fn radroots_core::currency::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::currency::RadrootsCoreCurrencyParseError
-pub radroots_core::currency::RadrootsCoreCurrencyParseError::InvalidFormat
-impl core::convert::From<radroots_core::currency::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::currency::ParseError) -> Self
-impl core::error::Error for radroots_core::currency::ParseError
-impl core::fmt::Display for radroots_core::currency::ParseError
-pub fn radroots_core::currency::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct radroots_core::currency::Currency(_)
impl radroots_core::currency::Currency
pub const radroots_core::currency::Currency::AUD: radroots_core::currency::Currency
@@ -41,33 +34,6 @@ impl serde_core::ser::Serialize for radroots_core::currency::Currency
pub fn radroots_core::currency::Currency::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
impl<'de> serde_core::de::Deserialize<'de> for radroots_core::currency::Currency
pub fn radroots_core::currency::Currency::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub struct radroots_core::currency::RadrootsCoreCurrency(_)
-impl radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::AUD: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::CAD: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::EUR: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::GBP: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::JPY: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::USD: radroots_core::currency::Currency
-pub const fn radroots_core::currency::Currency::as_bytes(&self) -> &[u8; 3]
-pub fn radroots_core::currency::Currency::as_str(&self) -> &str
-pub const fn radroots_core::currency::Currency::from_const([u8; 3]) -> core::result::Result<Self, radroots_core::currency::ParseError>
-pub fn radroots_core::currency::Currency::from_str_upper(&str) -> core::result::Result<Self, radroots_core::currency::ParseError>
-pub const fn radroots_core::currency::Currency::minor_unit_exponent(&self) -> u32
-impl core::convert::TryFrom<&str> for radroots_core::currency::Currency
-pub type radroots_core::currency::Currency::Error = radroots_core::currency::ParseError
-pub fn radroots_core::currency::Currency::try_from(&str) -> core::result::Result<Self, Self::Error>
-impl core::fmt::Debug for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::fmt::Display for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::currency::Currency
-pub type radroots_core::currency::Currency::Err = radroots_core::currency::ParseError
-pub fn radroots_core::currency::Currency::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub mod radroots_core::decimal
#[non_exhaustive] pub enum radroots_core::decimal::Error
pub radroots_core::decimal::Error::ArithmeticOverflow
@@ -85,7 +51,7 @@ pub fn radroots_core::quantity::Error::from(radroots_core::decimal::Error) -> Se
impl core::error::Error for radroots_core::decimal::Error
impl core::fmt::Display for radroots_core::decimal::Error
pub fn radroots_core::decimal::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub struct radroots_core::decimal::Decimal(pub rust_decimal::decimal::Decimal)
+pub struct radroots_core::decimal::Decimal(_)
impl radroots_core::decimal::Decimal
pub const radroots_core::decimal::Decimal::MAX: Self
pub const radroots_core::decimal::Decimal::MAX_SCALE: u32
@@ -100,7 +66,7 @@ pub fn radroots_core::decimal::Decimal::from_f64_display(f64) -> core::result::R
pub fn radroots_core::decimal::Decimal::from_str_exact(&str) -> core::result::Result<Self, radroots_core::decimal::Error>
pub fn radroots_core::decimal::Decimal::is_sign_negative(&self) -> bool
pub fn radroots_core::decimal::Decimal::is_zero(&self) -> bool
-pub fn radroots_core::decimal::Decimal::normalize(&self) -> rust_decimal::decimal::Decimal
+pub fn radroots_core::decimal::Decimal::normalize(&self) -> Self
pub fn radroots_core::decimal::Decimal::rescale(&mut self, u32)
pub fn radroots_core::decimal::Decimal::scale(&self) -> u32
pub fn radroots_core::decimal::Decimal::to_f64_lossy(&self) -> core::option::Option<f64>
@@ -110,106 +76,12 @@ impl core::convert::From<i32> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(i32) -> Self
impl core::convert::From<i64> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(i64) -> Self
-impl core::convert::From<radroots_core::decimal::Decimal> for rust_decimal::decimal::Decimal
-pub fn rust_decimal::decimal::Decimal::from(radroots_core::decimal::Decimal) -> Self
-impl core::convert::From<rust_decimal::decimal::Decimal> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(rust_decimal::decimal::Decimal) -> Self
impl core::convert::From<u32> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(u32) -> Self
impl core::convert::From<u64> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(u64) -> Self
impl core::fmt::Display for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Add for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::add(self, Self) -> Self
-impl core::ops::arith::Div for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::div(self, Self) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::mul(self, Self) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Sub for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::sub(self, Self) -> Self
-impl core::str::traits::FromStr for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Err = radroots_core::decimal::Error
-pub fn radroots_core::decimal::Decimal::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub struct radroots_core::decimal::RadrootsCoreDecimal(pub rust_decimal::decimal::Decimal)
-impl radroots_core::decimal::Decimal
-pub const radroots_core::decimal::Decimal::MAX: Self
-pub const radroots_core::decimal::Decimal::MAX_SCALE: u32
-pub const radroots_core::decimal::Decimal::MIN: Self
-pub const radroots_core::decimal::Decimal::ONE: Self
-pub const radroots_core::decimal::Decimal::ZERO: Self
-pub fn radroots_core::decimal::Decimal::checked_add(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_div(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_mul(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_sub(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::from_f64_display(f64) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::from_str_exact(&str) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::is_sign_negative(&self) -> bool
-pub fn radroots_core::decimal::Decimal::is_zero(&self) -> bool
-pub fn radroots_core::decimal::Decimal::normalize(&self) -> rust_decimal::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::rescale(&mut self, u32)
-pub fn radroots_core::decimal::Decimal::scale(&self) -> u32
-pub fn radroots_core::decimal::Decimal::to_f64_lossy(&self) -> core::option::Option<f64>
-pub fn radroots_core::decimal::Decimal::to_u64_exact(&self) -> core::option::Option<u64>
-pub fn radroots_core::decimal::Decimal::try_rescale_exact(&mut self, u32) -> core::result::Result<(), radroots_core::decimal::Error>
-impl core::convert::From<i32> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(i32) -> Self
-impl core::convert::From<i64> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(i64) -> Self
-impl core::convert::From<radroots_core::decimal::Decimal> for rust_decimal::decimal::Decimal
-pub fn rust_decimal::decimal::Decimal::from(radroots_core::decimal::Decimal) -> Self
-impl core::convert::From<rust_decimal::decimal::Decimal> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(rust_decimal::decimal::Decimal) -> Self
-impl core::convert::From<u32> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(u32) -> Self
-impl core::convert::From<u64> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(u64) -> Self
-impl core::fmt::Display for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Add for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::add(self, Self) -> Self
-impl core::ops::arith::Div for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::div(self, Self) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::mul(self, Self) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Sub for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::sub(self, Self) -> Self
impl core::str::traits::FromStr for radroots_core::decimal::Decimal
pub type radroots_core::decimal::Decimal::Err = radroots_core::decimal::Error
pub fn radroots_core::decimal::Decimal::from_str(&str) -> core::result::Result<Self, Self::Err>
@@ -234,60 +106,7 @@ pub fn radroots_core::Error::from(radroots_core::money::Error) -> Self
impl core::error::Error for radroots_core::money::Error
impl core::fmt::Display for radroots_core::money::Error
pub fn radroots_core::money::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::money::RadrootsCoreMoneyInvariantError
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::AmountOverflow
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::ArithmeticOverflow
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::CurrencyMismatch
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::DivisionByZero
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::NegativeAmount
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::NotWholeMinorUnits
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::PrecisionLoss
-pub radroots_core::money::RadrootsCoreMoneyInvariantError::ScaleOutOfRange
-impl core::convert::From<radroots_core::decimal::Error> for radroots_core::money::Error
-pub fn radroots_core::money::Error::from(radroots_core::decimal::Error) -> Self
-impl core::convert::From<radroots_core::money::Error> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::money::Error) -> Self
-impl core::error::Error for radroots_core::money::Error
-impl core::fmt::Display for radroots_core::money::Error
-pub fn radroots_core::money::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct radroots_core::money::Money
-pub radroots_core::money::Money::amount: radroots_core::decimal::Decimal
-pub radroots_core::money::Money::currency: radroots_core::currency::Currency
-impl radroots_core::money::Money
-pub const fn radroots_core::money::Money::amount(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::money::Money::checked_add(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_div_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_sub(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
-pub const fn radroots_core::money::Money::currency(&self) -> radroots_core::currency::Currency
-pub fn radroots_core::money::Money::div_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::money::Error>
-pub fn radroots_core::money::Money::from_minor_units_u32(u32, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::from_minor_units_u64(u64, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::is_zero(&self) -> bool
-pub fn radroots_core::money::Money::mul_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency(self) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency_with_strategy(self, rust_decimal::decimal::RoundingStrategy) -> Self
-pub fn radroots_core::money::Money::to_minor_units_u32_exact(&self) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_exact(&self) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::try_new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::try_with_scale_exact(self, u32) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::with_scale(self, u32) -> Self
-pub fn radroots_core::money::Money::zero(radroots_core::currency::Currency) -> Self
-impl core::fmt::Display for radroots_core::money::Money
-pub fn radroots_core::money::Money::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-pub struct radroots_core::money::RadrootsCoreMoney
-pub radroots_core::money::RadrootsCoreMoney::amount: radroots_core::decimal::Decimal
-pub radroots_core::money::RadrootsCoreMoney::currency: radroots_core::currency::Currency
impl radroots_core::money::Money
pub const fn radroots_core::money::Money::amount(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::money::Money::checked_add(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
@@ -295,31 +114,21 @@ pub fn radroots_core::money::Money::checked_div_decimal(&self, radroots_core::de
pub fn radroots_core::money::Money::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
pub fn radroots_core::money::Money::checked_sub(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
pub const fn radroots_core::money::Money::currency(&self) -> radroots_core::currency::Currency
-pub fn radroots_core::money::Money::div_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::money::Error>
pub fn radroots_core::money::Money::from_minor_units_u32(u32, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::from_minor_units_u64(u64, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::is_zero(&self) -> bool
-pub fn radroots_core::money::Money::mul_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::quantize_to_currency(self) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency_with_strategy(self, rust_decimal::decimal::RoundingStrategy) -> Self
pub fn radroots_core::money::Money::to_minor_units_u32_exact(&self) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u32, radroots_core::money::Error>
+pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self) -> core::result::Result<u32, radroots_core::money::Error>
pub fn radroots_core::money::Money::to_minor_units_u64_exact(&self) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u64, radroots_core::money::Error>
+pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self) -> core::result::Result<u64, radroots_core::money::Error>
pub fn radroots_core::money::Money::try_new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> core::result::Result<Self, radroots_core::money::Error>
pub fn radroots_core::money::Money::try_with_scale_exact(self, u32) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::with_scale(self, u32) -> Self
pub fn radroots_core::money::Money::zero(radroots_core::currency::Currency) -> Self
impl core::fmt::Display for radroots_core::money::Money
pub fn radroots_core::money::Money::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::money::Money
+pub fn radroots_core::money::Money::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub mod radroots_core::percent
#[non_exhaustive] pub enum radroots_core::percent::ParseError
pub radroots_core::percent::ParseError::InvalidNumber
@@ -328,42 +137,14 @@ pub fn radroots_core::Error::from(radroots_core::percent::ParseError) -> Self
impl core::error::Error for radroots_core::percent::ParseError
impl core::fmt::Display for radroots_core::percent::ParseError
pub fn radroots_core::percent::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::percent::RadrootsCorePercentParseError
-pub radroots_core::percent::RadrootsCorePercentParseError::InvalidNumber
-impl core::convert::From<radroots_core::percent::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::percent::ParseError) -> Self
-impl core::error::Error for radroots_core::percent::ParseError
-impl core::fmt::Display for radroots_core::percent::ParseError
-pub fn radroots_core::percent::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct radroots_core::percent::Percent
-pub radroots_core::percent::Percent::value: radroots_core::decimal::Decimal
-impl radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::from_ratio(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::new(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::of_money(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::of_money_quantized(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::to_ratio(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::percent::Percent::try_from_ratio(radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::percent::Percent::try_of_money(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
-pub fn radroots_core::percent::Percent::try_of_money_quantized(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
-pub fn radroots_core::percent::Percent::try_to_ratio(&self) -> core::result::Result<radroots_core::decimal::Decimal, radroots_core::decimal::Error>
-impl core::fmt::Display for radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::percent::Percent
-pub type radroots_core::percent::Percent::Err = radroots_core::percent::ParseError
-pub fn radroots_core::percent::Percent::from_str(&str) -> core::result::Result<Self, Self::Err>
-pub struct radroots_core::percent::RadrootsCorePercent
-pub radroots_core::percent::RadrootsCorePercent::value: radroots_core::decimal::Decimal
impl radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::from_ratio(radroots_core::decimal::Decimal) -> Self
pub fn radroots_core::percent::Percent::new(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::of_money(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::of_money_quantized(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::to_ratio(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::percent::Percent::try_from_ratio(radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::decimal::Error>
pub fn radroots_core::percent::Percent::try_of_money(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
pub fn radroots_core::percent::Percent::try_of_money_quantized(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
pub fn radroots_core::percent::Percent::try_to_ratio(&self) -> core::result::Result<radroots_core::decimal::Decimal, radroots_core::decimal::Error>
+pub const fn radroots_core::percent::Percent::value(&self) -> radroots_core::decimal::Decimal
impl core::fmt::Display for radroots_core::percent::Percent
pub fn radroots_core::percent::Percent::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::str::traits::FromStr for radroots_core::percent::Percent
@@ -402,54 +183,43 @@ pub radroots_core::pricing::Error::PerQuantityZero
pub radroots_core::pricing::Error::UnitMismatch
pub radroots_core::pricing::Error::UnitMismatch::have: radroots_core::unit::Unit
pub radroots_core::pricing::Error::UnitMismatch::want: radroots_core::unit::Unit
-impl core::convert::From<radroots_core::RadrootsCoreQuantityPriceError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::RadrootsCoreQuantityPriceError) -> Self
-impl core::error::Error for radroots_core::RadrootsCoreQuantityPriceError
-impl core::fmt::Display for radroots_core::RadrootsCoreQuantityPriceError
-pub fn radroots_core::RadrootsCoreQuantityPriceError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl core::convert::From<radroots_core::pricing::Error> for radroots_core::Error
+pub fn radroots_core::Error::from(radroots_core::pricing::Error) -> Self
+impl core::error::Error for radroots_core::pricing::Error
+impl core::fmt::Display for radroots_core::pricing::Error
+pub fn radroots_core::pricing::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct radroots_core::pricing::Discount
-pub radroots_core::pricing::Discount::scope: radroots_core::RadrootsCoreDiscountScope
-pub radroots_core::pricing::Discount::threshold: radroots_core::RadrootsCoreDiscountThreshold
-pub radroots_core::pricing::Discount::value: radroots_core::RadrootsCoreDiscountValue
-impl radroots_core::RadrootsCoreDiscount
-pub fn radroots_core::RadrootsCoreDiscount::is_non_negative(&self) -> bool
-pub fn radroots_core::RadrootsCoreDiscount::scope(&self) -> &radroots_core::RadrootsCoreDiscountScope
-pub fn radroots_core::RadrootsCoreDiscount::threshold(&self) -> &radroots_core::RadrootsCoreDiscountThreshold
-pub fn radroots_core::RadrootsCoreDiscount::try_new(radroots_core::RadrootsCoreDiscountScope, radroots_core::RadrootsCoreDiscountThreshold, radroots_core::RadrootsCoreDiscountValue) -> core::result::Result<Self, radroots_core::pricing::DiscountError>
-pub fn radroots_core::RadrootsCoreDiscount::validate(&self) -> core::result::Result<(), radroots_core::pricing::DiscountError>
-pub fn radroots_core::RadrootsCoreDiscount::value(&self) -> &radroots_core::RadrootsCoreDiscountValue
+impl radroots_core::pricing::Discount
+pub fn radroots_core::pricing::Discount::is_non_negative(&self) -> bool
+pub fn radroots_core::pricing::Discount::scope(&self) -> &radroots_core::pricing::DiscountScope
+pub fn radroots_core::pricing::Discount::threshold(&self) -> &radroots_core::pricing::DiscountThreshold
+pub fn radroots_core::pricing::Discount::try_new(radroots_core::pricing::DiscountScope, radroots_core::pricing::DiscountThreshold, radroots_core::pricing::DiscountValue) -> core::result::Result<Self, radroots_core::pricing::DiscountError>
+pub fn radroots_core::pricing::Discount::validate(&self) -> core::result::Result<(), radroots_core::pricing::DiscountError>
+pub fn radroots_core::pricing::Discount::value(&self) -> &radroots_core::pricing::DiscountValue
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::pricing::Discount
+pub fn radroots_core::pricing::Discount::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub struct radroots_core::pricing::QuantityPrice
-pub radroots_core::pricing::QuantityPrice::amount: radroots_core::money::Money
-pub radroots_core::pricing::QuantityPrice::quantity: radroots_core::quantity::Quantity
impl radroots_core::QuantityPrice
pub fn radroots_core::QuantityPrice::amount(&self) -> &radroots_core::money::Money
pub fn radroots_core::QuantityPrice::is_price_per_canonical_unit(&self) -> bool
-pub fn radroots_core::QuantityPrice::new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> Self
pub fn radroots_core::QuantityPrice::quantity(&self) -> &radroots_core::quantity::Quantity
-pub fn radroots_core::QuantityPrice::try_cost_for_amount_in(&self, radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_quantity_in(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> core::result::Result<Self, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_canonical_unit_price(&self) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_unit_price(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::validate(&self) -> core::result::Result<(), radroots_core::RadrootsCoreQuantityPriceError>
-impl radroots_core::RadrootsCoreQuantityPriceOps for radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
+pub fn radroots_core::QuantityPrice::try_cost_for_amount_in(&self, radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_cost_for_quantity_in(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> core::result::Result<Self, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_to_canonical_unit_price(&self) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_to_unit_price(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::validate(&self) -> core::result::Result<(), radroots_core::pricing::Error>
+impl radroots_core::pricing::QuantityPriceOps for radroots_core::QuantityPrice
+pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::QuantityPrice
+pub fn radroots_core::QuantityPrice::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub trait radroots_core::pricing::QuantityPriceOps: radroots_core::quantity_price::sealed::Sealed
-pub fn radroots_core::pricing::QuantityPriceOps::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::pricing::QuantityPriceOps::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::pricing::QuantityPriceOps::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::pricing::QuantityPriceOps::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::pricing::QuantityPriceOps::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-impl radroots_core::RadrootsCoreQuantityPriceOps for radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
+pub fn radroots_core::pricing::QuantityPriceOps::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::pricing::QuantityPriceOps::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+impl radroots_core::pricing::QuantityPriceOps for radroots_core::QuantityPrice
+pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
pub mod radroots_core::quantity
#[non_exhaustive] pub enum radroots_core::quantity::Error
pub radroots_core::quantity::Error::ArithmeticOverflow
@@ -463,22 +233,7 @@ pub fn radroots_core::Error::from(radroots_core::quantity::Error) -> Self
impl core::error::Error for radroots_core::quantity::Error
impl core::fmt::Display for radroots_core::quantity::Error
pub fn radroots_core::quantity::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::quantity::RadrootsCoreQuantityInvariantError
-pub radroots_core::quantity::RadrootsCoreQuantityInvariantError::ArithmeticOverflow
-pub radroots_core::quantity::RadrootsCoreQuantityInvariantError::DivisionByZero
-pub radroots_core::quantity::RadrootsCoreQuantityInvariantError::NegativeAmount
-pub radroots_core::quantity::RadrootsCoreQuantityInvariantError::UnitMismatch
-impl core::convert::From<radroots_core::decimal::Error> for radroots_core::quantity::Error
-pub fn radroots_core::quantity::Error::from(radroots_core::decimal::Error) -> Self
-impl core::convert::From<radroots_core::quantity::Error> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::quantity::Error) -> Self
-impl core::error::Error for radroots_core::quantity::Error
-impl core::fmt::Display for radroots_core::quantity::Error
-pub fn radroots_core::quantity::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct radroots_core::quantity::Quantity
-pub radroots_core::quantity::Quantity::amount: radroots_core::decimal::Decimal
-pub radroots_core::quantity::Quantity::label: core::option::Option<alloc::string::String>
-pub radroots_core::quantity::Quantity::unit: radroots_core::unit::Unit
impl radroots_core::quantity::Quantity
pub const fn radroots_core::quantity::Quantity::amount(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::quantity::Quantity::canonical_unit(&self) -> radroots_core::unit::Unit
@@ -487,13 +242,9 @@ pub fn radroots_core::quantity::Quantity::checked_div_decimal(&self, radroots_co
pub fn radroots_core::quantity::Quantity::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::checked_sub(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
pub fn radroots_core::quantity::Quantity::clear_label(self) -> Self
-pub fn radroots_core::quantity::Quantity::div_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::is_canonical(&self) -> bool
pub fn radroots_core::quantity::Quantity::is_zero(&self) -> bool
pub fn radroots_core::quantity::Quantity::label(&self) -> core::option::Option<&str>
-pub fn radroots_core::quantity::Quantity::mul_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> Self
pub fn radroots_core::quantity::Quantity::to_canonical(&self) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
pub fn radroots_core::quantity::Quantity::try_add(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::try_convert_to(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
@@ -502,53 +253,11 @@ pub fn radroots_core::quantity::Quantity::try_sub(&self, &radroots_core::quantit
pub const fn radroots_core::quantity::Quantity::unit(&self) -> radroots_core::unit::Unit
pub fn radroots_core::quantity::Quantity::with_label<S: core::convert::Into<alloc::string::String>>(self, S) -> Self
pub fn radroots_core::quantity::Quantity::with_optional_label<S: core::convert::Into<alloc::string::String>>(self, core::option::Option<S>) -> Self
-pub fn radroots_core::quantity::Quantity::with_scale(self, u32) -> Self
pub fn radroots_core::quantity::Quantity::zero(radroots_core::unit::Unit) -> Self
impl core::fmt::Display for radroots_core::quantity::Quantity
pub fn radroots_core::quantity::Quantity::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub struct radroots_core::quantity::RadrootsCoreQuantity
-pub radroots_core::quantity::RadrootsCoreQuantity::amount: radroots_core::decimal::Decimal
-pub radroots_core::quantity::RadrootsCoreQuantity::label: core::option::Option<alloc::string::String>
-pub radroots_core::quantity::RadrootsCoreQuantity::unit: radroots_core::unit::Unit
-impl radroots_core::quantity::Quantity
-pub const fn radroots_core::quantity::Quantity::amount(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::quantity::Quantity::canonical_unit(&self) -> radroots_core::unit::Unit
-pub fn radroots_core::quantity::Quantity::checked_add(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
-pub fn radroots_core::quantity::Quantity::checked_div_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::checked_sub(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
-pub fn radroots_core::quantity::Quantity::clear_label(self) -> Self
-pub fn radroots_core::quantity::Quantity::div_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::is_canonical(&self) -> bool
-pub fn radroots_core::quantity::Quantity::is_zero(&self) -> bool
-pub fn radroots_core::quantity::Quantity::label(&self) -> core::option::Option<&str>
-pub fn radroots_core::quantity::Quantity::mul_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> Self
-pub fn radroots_core::quantity::Quantity::to_canonical(&self) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
-pub fn radroots_core::quantity::Quantity::try_add(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::try_convert_to(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
-pub fn radroots_core::quantity::Quantity::try_new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<Self, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::try_sub(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub const fn radroots_core::quantity::Quantity::unit(&self) -> radroots_core::unit::Unit
-pub fn radroots_core::quantity::Quantity::with_label<S: core::convert::Into<alloc::string::String>>(self, S) -> Self
-pub fn radroots_core::quantity::Quantity::with_optional_label<S: core::convert::Into<alloc::string::String>>(self, core::option::Option<S>) -> Self
-pub fn radroots_core::quantity::Quantity::with_scale(self, u32) -> Self
-pub fn radroots_core::quantity::Quantity::zero(radroots_core::unit::Unit) -> Self
-impl core::fmt::Display for radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::quantity::Quantity
+pub fn radroots_core::quantity::Quantity::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub mod radroots_core::unit
pub enum radroots_core::unit::ConvertError
pub radroots_core::unit::ConvertError::ArithmeticOverflow
@@ -577,62 +286,6 @@ pub fn radroots_core::Error::from(radroots_core::unit::ParseError) -> Self
impl core::error::Error for radroots_core::unit::ParseError
impl core::fmt::Display for radroots_core::unit::ParseError
pub fn radroots_core::unit::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::unit::RadrootsCoreUnit
-pub radroots_core::unit::RadrootsCoreUnit::Each
-pub radroots_core::unit::RadrootsCoreUnit::MassG
-pub radroots_core::unit::RadrootsCoreUnit::MassKg
-pub radroots_core::unit::RadrootsCoreUnit::MassLb
-pub radroots_core::unit::RadrootsCoreUnit::MassOz
-pub radroots_core::unit::RadrootsCoreUnit::VolumeL
-pub radroots_core::unit::RadrootsCoreUnit::VolumeMl
-impl radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::canonical_unit(&self) -> Self
-pub fn radroots_core::unit::Unit::code(&self) -> &'static str
-pub fn radroots_core::unit::Unit::dimension(&self) -> radroots_core::unit::UnitDimension
-pub fn radroots_core::unit::Unit::is_count(&self) -> bool
-pub fn radroots_core::unit::Unit::is_mass(&self) -> bool
-pub fn radroots_core::unit::Unit::is_volume(&self) -> bool
-pub fn radroots_core::unit::Unit::same_dimension(Self, Self) -> bool
-impl core::fmt::Display for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::unit::Unit
-pub type radroots_core::unit::Unit::Err = radroots_core::unit::ParseError
-pub fn radroots_core::unit::Unit::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub enum radroots_core::unit::RadrootsCoreUnitConvertError
-pub radroots_core::unit::RadrootsCoreUnitConvertError::ArithmeticOverflow
-pub radroots_core::unit::RadrootsCoreUnitConvertError::ArithmeticOverflow::from: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::ArithmeticOverflow::to: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotConvertibleUnits
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotConvertibleUnits::from: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotConvertibleUnits::to: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotMassUnit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotMassUnit::from: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotMassUnit::to: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotVolumeUnit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotVolumeUnit::from: radroots_core::unit::Unit
-pub radroots_core::unit::RadrootsCoreUnitConvertError::NotVolumeUnit::to: radroots_core::unit::Unit
-impl core::convert::From<radroots_core::unit::ConvertError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::unit::ConvertError) -> Self
-impl core::error::Error for radroots_core::unit::ConvertError
-impl core::fmt::Display for radroots_core::unit::ConvertError
-pub fn radroots_core::unit::ConvertError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::unit::RadrootsCoreUnitDimension
-pub radroots_core::unit::RadrootsCoreUnitDimension::Count
-pub radroots_core::unit::RadrootsCoreUnitDimension::Mass
-pub radroots_core::unit::RadrootsCoreUnitDimension::Volume
-pub enum radroots_core::unit::RadrootsCoreUnitParseError
-pub radroots_core::unit::RadrootsCoreUnitParseError::NotAMassUnit
-pub radroots_core::unit::RadrootsCoreUnitParseError::NotAVolumeUnit
-pub radroots_core::unit::RadrootsCoreUnitParseError::UnknownUnit
-impl core::convert::From<radroots_core::unit::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::unit::ParseError) -> Self
-impl core::error::Error for radroots_core::unit::ParseError
-impl core::fmt::Display for radroots_core::unit::ParseError
-pub fn radroots_core::unit::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub enum radroots_core::unit::Unit
pub radroots_core::unit::Unit::Each
pub radroots_core::unit::Unit::MassG
@@ -673,12 +326,10 @@ pub radroots_core::Error::Decimal(radroots_core::decimal::Error)
pub radroots_core::Error::Discount(radroots_core::pricing::DiscountError)
pub radroots_core::Error::Money(radroots_core::money::Error)
pub radroots_core::Error::Percent(radroots_core::percent::ParseError)
-pub radroots_core::Error::Pricing(radroots_core::RadrootsCoreQuantityPriceError)
+pub radroots_core::Error::Pricing(radroots_core::pricing::Error)
pub radroots_core::Error::Quantity(radroots_core::quantity::Error)
pub radroots_core::Error::UnitConversion(radroots_core::unit::ConvertError)
pub radroots_core::Error::UnitParse(radroots_core::unit::ParseError)
-impl core::convert::From<radroots_core::RadrootsCoreQuantityPriceError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::RadrootsCoreQuantityPriceError) -> Self
impl core::convert::From<radroots_core::currency::ParseError> for radroots_core::Error
pub fn radroots_core::Error::from(radroots_core::currency::ParseError) -> Self
impl core::convert::From<radroots_core::decimal::Error> for radroots_core::Error
@@ -689,6 +340,8 @@ impl core::convert::From<radroots_core::percent::ParseError> for radroots_core::
pub fn radroots_core::Error::from(radroots_core::percent::ParseError) -> Self
impl core::convert::From<radroots_core::pricing::DiscountError> for radroots_core::Error
pub fn radroots_core::Error::from(radroots_core::pricing::DiscountError) -> Self
+impl core::convert::From<radroots_core::pricing::Error> for radroots_core::Error
+pub fn radroots_core::Error::from(radroots_core::pricing::Error) -> Self
impl core::convert::From<radroots_core::quantity::Error> for radroots_core::Error
pub fn radroots_core::Error::from(radroots_core::quantity::Error) -> Self
impl core::convert::From<radroots_core::unit::ConvertError> for radroots_core::Error
@@ -699,133 +352,6 @@ impl core::error::Error for radroots_core::Error
pub fn radroots_core::Error::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
impl core::fmt::Display for radroots_core::Error
pub fn radroots_core::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::RadrootsCoreCurrencyParseError
-pub radroots_core::RadrootsCoreCurrencyParseError::InvalidFormat
-impl core::convert::From<radroots_core::currency::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::currency::ParseError) -> Self
-impl core::error::Error for radroots_core::currency::ParseError
-impl core::fmt::Display for radroots_core::currency::ParseError
-pub fn radroots_core::currency::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::RadrootsCoreDiscountScope
-pub radroots_core::RadrootsCoreDiscountScope::Bin
-pub radroots_core::RadrootsCoreDiscountScope::OrderTotal
-pub enum radroots_core::RadrootsCoreDiscountThreshold
-pub radroots_core::RadrootsCoreDiscountThreshold::BinCount
-pub radroots_core::RadrootsCoreDiscountThreshold::BinCount::bin_id: alloc::string::String
-pub radroots_core::RadrootsCoreDiscountThreshold::BinCount::min: u32
-pub radroots_core::RadrootsCoreDiscountThreshold::OrderQuantity
-pub radroots_core::RadrootsCoreDiscountThreshold::OrderQuantity::min: radroots_core::quantity::Quantity
-pub enum radroots_core::RadrootsCoreDiscountValue
-pub radroots_core::RadrootsCoreDiscountValue::MoneyPerBin(radroots_core::money::Money)
-pub radroots_core::RadrootsCoreDiscountValue::Percent(radroots_core::percent::Percent)
-#[non_exhaustive] pub enum radroots_core::RadrootsCoreMoneyInvariantError
-pub radroots_core::RadrootsCoreMoneyInvariantError::AmountOverflow
-pub radroots_core::RadrootsCoreMoneyInvariantError::ArithmeticOverflow
-pub radroots_core::RadrootsCoreMoneyInvariantError::CurrencyMismatch
-pub radroots_core::RadrootsCoreMoneyInvariantError::DivisionByZero
-pub radroots_core::RadrootsCoreMoneyInvariantError::NegativeAmount
-pub radroots_core::RadrootsCoreMoneyInvariantError::NotWholeMinorUnits
-pub radroots_core::RadrootsCoreMoneyInvariantError::PrecisionLoss
-pub radroots_core::RadrootsCoreMoneyInvariantError::ScaleOutOfRange
-impl core::convert::From<radroots_core::decimal::Error> for radroots_core::money::Error
-pub fn radroots_core::money::Error::from(radroots_core::decimal::Error) -> Self
-impl core::convert::From<radroots_core::money::Error> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::money::Error) -> Self
-impl core::error::Error for radroots_core::money::Error
-impl core::fmt::Display for radroots_core::money::Error
-pub fn radroots_core::money::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::RadrootsCorePercentParseError
-pub radroots_core::RadrootsCorePercentParseError::InvalidNumber
-impl core::convert::From<radroots_core::percent::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::percent::ParseError) -> Self
-impl core::error::Error for radroots_core::percent::ParseError
-impl core::fmt::Display for radroots_core::percent::ParseError
-pub fn radroots_core::percent::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-#[non_exhaustive] pub enum radroots_core::RadrootsCoreQuantityInvariantError
-pub radroots_core::RadrootsCoreQuantityInvariantError::ArithmeticOverflow
-pub radroots_core::RadrootsCoreQuantityInvariantError::DivisionByZero
-pub radroots_core::RadrootsCoreQuantityInvariantError::NegativeAmount
-pub radroots_core::RadrootsCoreQuantityInvariantError::UnitMismatch
-impl core::convert::From<radroots_core::decimal::Error> for radroots_core::quantity::Error
-pub fn radroots_core::quantity::Error::from(radroots_core::decimal::Error) -> Self
-impl core::convert::From<radroots_core::quantity::Error> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::quantity::Error) -> Self
-impl core::error::Error for radroots_core::quantity::Error
-impl core::fmt::Display for radroots_core::quantity::Error
-pub fn radroots_core::quantity::Error::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::RadrootsCoreQuantityPriceError
-pub radroots_core::RadrootsCoreQuantityPriceError::ArithmeticOverflow
-pub radroots_core::RadrootsCoreQuantityPriceError::NegativePrice
-pub radroots_core::RadrootsCoreQuantityPriceError::NegativeRequestedQuantity
-pub radroots_core::RadrootsCoreQuantityPriceError::NonConvertibleUnits
-pub radroots_core::RadrootsCoreQuantityPriceError::NonConvertibleUnits::from: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreQuantityPriceError::NonConvertibleUnits::to: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreQuantityPriceError::PerQuantityNegative
-pub radroots_core::RadrootsCoreQuantityPriceError::PerQuantityZero
-pub radroots_core::RadrootsCoreQuantityPriceError::UnitMismatch
-pub radroots_core::RadrootsCoreQuantityPriceError::UnitMismatch::have: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreQuantityPriceError::UnitMismatch::want: radroots_core::unit::Unit
-impl core::convert::From<radroots_core::RadrootsCoreQuantityPriceError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::RadrootsCoreQuantityPriceError) -> Self
-impl core::error::Error for radroots_core::RadrootsCoreQuantityPriceError
-impl core::fmt::Display for radroots_core::RadrootsCoreQuantityPriceError
-pub fn radroots_core::RadrootsCoreQuantityPriceError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::RadrootsCoreUnit
-pub radroots_core::RadrootsCoreUnit::Each
-pub radroots_core::RadrootsCoreUnit::MassG
-pub radroots_core::RadrootsCoreUnit::MassKg
-pub radroots_core::RadrootsCoreUnit::MassLb
-pub radroots_core::RadrootsCoreUnit::MassOz
-pub radroots_core::RadrootsCoreUnit::VolumeL
-pub radroots_core::RadrootsCoreUnit::VolumeMl
-impl radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::canonical_unit(&self) -> Self
-pub fn radroots_core::unit::Unit::code(&self) -> &'static str
-pub fn radroots_core::unit::Unit::dimension(&self) -> radroots_core::unit::UnitDimension
-pub fn radroots_core::unit::Unit::is_count(&self) -> bool
-pub fn radroots_core::unit::Unit::is_mass(&self) -> bool
-pub fn radroots_core::unit::Unit::is_volume(&self) -> bool
-pub fn radroots_core::unit::Unit::same_dimension(Self, Self) -> bool
-impl core::fmt::Display for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::unit::Unit
-pub type radroots_core::unit::Unit::Err = radroots_core::unit::ParseError
-pub fn radroots_core::unit::Unit::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::unit::Unit
-pub fn radroots_core::unit::Unit::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub enum radroots_core::RadrootsCoreUnitConvertError
-pub radroots_core::RadrootsCoreUnitConvertError::ArithmeticOverflow
-pub radroots_core::RadrootsCoreUnitConvertError::ArithmeticOverflow::from: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::ArithmeticOverflow::to: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotConvertibleUnits
-pub radroots_core::RadrootsCoreUnitConvertError::NotConvertibleUnits::from: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotConvertibleUnits::to: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotMassUnit
-pub radroots_core::RadrootsCoreUnitConvertError::NotMassUnit::from: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotMassUnit::to: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotVolumeUnit
-pub radroots_core::RadrootsCoreUnitConvertError::NotVolumeUnit::from: radroots_core::unit::Unit
-pub radroots_core::RadrootsCoreUnitConvertError::NotVolumeUnit::to: radroots_core::unit::Unit
-impl core::convert::From<radroots_core::unit::ConvertError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::unit::ConvertError) -> Self
-impl core::error::Error for radroots_core::unit::ConvertError
-impl core::fmt::Display for radroots_core::unit::ConvertError
-pub fn radroots_core::unit::ConvertError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub enum radroots_core::RadrootsCoreUnitDimension
-pub radroots_core::RadrootsCoreUnitDimension::Count
-pub radroots_core::RadrootsCoreUnitDimension::Mass
-pub radroots_core::RadrootsCoreUnitDimension::Volume
-pub enum radroots_core::RadrootsCoreUnitParseError
-pub radroots_core::RadrootsCoreUnitParseError::NotAMassUnit
-pub radroots_core::RadrootsCoreUnitParseError::NotAVolumeUnit
-pub radroots_core::RadrootsCoreUnitParseError::UnknownUnit
-impl core::convert::From<radroots_core::unit::ParseError> for radroots_core::Error
-pub fn radroots_core::Error::from(radroots_core::unit::ParseError) -> Self
-impl core::error::Error for radroots_core::unit::ParseError
-impl core::fmt::Display for radroots_core::unit::ParseError
-pub fn radroots_core::unit::ParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub enum radroots_core::Unit
pub radroots_core::Unit::Each
pub radroots_core::Unit::MassG
@@ -878,7 +404,7 @@ impl serde_core::ser::Serialize for radroots_core::currency::Currency
pub fn radroots_core::currency::Currency::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
impl<'de> serde_core::de::Deserialize<'de> for radroots_core::currency::Currency
pub fn radroots_core::currency::Currency::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub struct radroots_core::Decimal(pub rust_decimal::decimal::Decimal)
+pub struct radroots_core::Decimal(_)
impl radroots_core::decimal::Decimal
pub const radroots_core::decimal::Decimal::MAX: Self
pub const radroots_core::decimal::Decimal::MAX_SCALE: u32
@@ -893,7 +419,7 @@ pub fn radroots_core::decimal::Decimal::from_f64_display(f64) -> core::result::R
pub fn radroots_core::decimal::Decimal::from_str_exact(&str) -> core::result::Result<Self, radroots_core::decimal::Error>
pub fn radroots_core::decimal::Decimal::is_sign_negative(&self) -> bool
pub fn radroots_core::decimal::Decimal::is_zero(&self) -> bool
-pub fn radroots_core::decimal::Decimal::normalize(&self) -> rust_decimal::decimal::Decimal
+pub fn radroots_core::decimal::Decimal::normalize(&self) -> Self
pub fn radroots_core::decimal::Decimal::rescale(&mut self, u32)
pub fn radroots_core::decimal::Decimal::scale(&self) -> u32
pub fn radroots_core::decimal::Decimal::to_f64_lossy(&self) -> core::option::Option<f64>
@@ -903,40 +429,12 @@ impl core::convert::From<i32> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(i32) -> Self
impl core::convert::From<i64> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(i64) -> Self
-impl core::convert::From<radroots_core::decimal::Decimal> for rust_decimal::decimal::Decimal
-pub fn rust_decimal::decimal::Decimal::from(radroots_core::decimal::Decimal) -> Self
-impl core::convert::From<rust_decimal::decimal::Decimal> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(rust_decimal::decimal::Decimal) -> Self
impl core::convert::From<u32> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(u32) -> Self
impl core::convert::From<u64> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::from(u64) -> Self
impl core::fmt::Display for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Add for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::add(self, Self) -> Self
-impl core::ops::arith::Div for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::div(self, Self) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::mul(self, Self) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Sub for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::sub(self, Self) -> Self
impl core::str::traits::FromStr for radroots_core::decimal::Decimal
pub type radroots_core::decimal::Decimal::Err = radroots_core::decimal::Error
pub fn radroots_core::decimal::Decimal::from_str(&str) -> core::result::Result<Self, Self::Err>
@@ -945,8 +443,6 @@ pub fn radroots_core::decimal::Decimal::serialize<S: serde_core::ser::Serializer
impl<'de> serde_core::de::Deserialize<'de> for radroots_core::decimal::Decimal
pub fn radroots_core::decimal::Decimal::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub struct radroots_core::Money
-pub radroots_core::Money::amount: radroots_core::decimal::Decimal
-pub radroots_core::Money::currency: radroots_core::currency::Currency
impl radroots_core::money::Money
pub const fn radroots_core::money::Money::amount(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::money::Money::checked_add(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
@@ -954,52 +450,35 @@ pub fn radroots_core::money::Money::checked_div_decimal(&self, radroots_core::de
pub fn radroots_core::money::Money::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
pub fn radroots_core::money::Money::checked_sub(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
pub const fn radroots_core::money::Money::currency(&self) -> radroots_core::currency::Currency
-pub fn radroots_core::money::Money::div_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::money::Error>
pub fn radroots_core::money::Money::from_minor_units_u32(u32, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::from_minor_units_u64(u64, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::is_zero(&self) -> bool
-pub fn radroots_core::money::Money::mul_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> Self
pub fn radroots_core::money::Money::quantize_to_currency(self) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency_with_strategy(self, rust_decimal::decimal::RoundingStrategy) -> Self
pub fn radroots_core::money::Money::to_minor_units_u32_exact(&self) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u32, radroots_core::money::Error>
+pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self) -> core::result::Result<u32, radroots_core::money::Error>
pub fn radroots_core::money::Money::to_minor_units_u64_exact(&self) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u64, radroots_core::money::Error>
+pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self) -> core::result::Result<u64, radroots_core::money::Error>
pub fn radroots_core::money::Money::try_new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> core::result::Result<Self, radroots_core::money::Error>
pub fn radroots_core::money::Money::try_with_scale_exact(self, u32) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::with_scale(self, u32) -> Self
pub fn radroots_core::money::Money::zero(radroots_core::currency::Currency) -> Self
impl core::fmt::Display for radroots_core::money::Money
pub fn radroots_core::money::Money::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::money::Money
+pub fn radroots_core::money::Money::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub struct radroots_core::Percent
-pub radroots_core::Percent::value: radroots_core::decimal::Decimal
impl radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::from_ratio(radroots_core::decimal::Decimal) -> Self
pub fn radroots_core::percent::Percent::new(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::of_money(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::of_money_quantized(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::to_ratio(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::percent::Percent::try_from_ratio(radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::decimal::Error>
pub fn radroots_core::percent::Percent::try_of_money(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
pub fn radroots_core::percent::Percent::try_of_money_quantized(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
pub fn radroots_core::percent::Percent::try_to_ratio(&self) -> core::result::Result<radroots_core::decimal::Decimal, radroots_core::decimal::Error>
+pub const fn radroots_core::percent::Percent::value(&self) -> radroots_core::decimal::Decimal
impl core::fmt::Display for radroots_core::percent::Percent
pub fn radroots_core::percent::Percent::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::str::traits::FromStr for radroots_core::percent::Percent
pub type radroots_core::percent::Percent::Err = radroots_core::percent::ParseError
pub fn radroots_core::percent::Percent::from_str(&str) -> core::result::Result<Self, Self::Err>
pub struct radroots_core::Quantity
-pub radroots_core::Quantity::amount: radroots_core::decimal::Decimal
-pub radroots_core::Quantity::label: core::option::Option<alloc::string::String>
-pub radroots_core::Quantity::unit: radroots_core::unit::Unit
impl radroots_core::quantity::Quantity
pub const fn radroots_core::quantity::Quantity::amount(&self) -> radroots_core::decimal::Decimal
pub fn radroots_core::quantity::Quantity::canonical_unit(&self) -> radroots_core::unit::Unit
@@ -1008,13 +487,9 @@ pub fn radroots_core::quantity::Quantity::checked_div_decimal(&self, radroots_co
pub fn radroots_core::quantity::Quantity::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::checked_sub(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
pub fn radroots_core::quantity::Quantity::clear_label(self) -> Self
-pub fn radroots_core::quantity::Quantity::div_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::is_canonical(&self) -> bool
pub fn radroots_core::quantity::Quantity::is_zero(&self) -> bool
pub fn radroots_core::quantity::Quantity::label(&self) -> core::option::Option<&str>
-pub fn radroots_core::quantity::Quantity::mul_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> Self
pub fn radroots_core::quantity::Quantity::to_canonical(&self) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
pub fn radroots_core::quantity::Quantity::try_add(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
pub fn radroots_core::quantity::Quantity::try_convert_to(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
@@ -1023,258 +498,24 @@ pub fn radroots_core::quantity::Quantity::try_sub(&self, &radroots_core::quantit
pub const fn radroots_core::quantity::Quantity::unit(&self) -> radroots_core::unit::Unit
pub fn radroots_core::quantity::Quantity::with_label<S: core::convert::Into<alloc::string::String>>(self, S) -> Self
pub fn radroots_core::quantity::Quantity::with_optional_label<S: core::convert::Into<alloc::string::String>>(self, core::option::Option<S>) -> Self
-pub fn radroots_core::quantity::Quantity::with_scale(self, u32) -> Self
pub fn radroots_core::quantity::Quantity::zero(radroots_core::unit::Unit) -> Self
impl core::fmt::Display for radroots_core::quantity::Quantity
pub fn radroots_core::quantity::Quantity::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::quantity::Quantity
+pub fn radroots_core::quantity::Quantity::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
pub struct radroots_core::QuantityPrice
-pub radroots_core::QuantityPrice::amount: radroots_core::money::Money
-pub radroots_core::QuantityPrice::quantity: radroots_core::quantity::Quantity
-impl radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::amount(&self) -> &radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::is_price_per_canonical_unit(&self) -> bool
-pub fn radroots_core::QuantityPrice::new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> Self
-pub fn radroots_core::QuantityPrice::quantity(&self) -> &radroots_core::quantity::Quantity
-pub fn radroots_core::QuantityPrice::try_cost_for_amount_in(&self, radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_quantity_in(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> core::result::Result<Self, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_canonical_unit_price(&self) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_unit_price(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::validate(&self) -> core::result::Result<(), radroots_core::RadrootsCoreQuantityPriceError>
-impl radroots_core::RadrootsCoreQuantityPriceOps for radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub struct radroots_core::RadrootsCoreCurrency(_)
-impl radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::AUD: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::CAD: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::EUR: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::GBP: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::JPY: radroots_core::currency::Currency
-pub const radroots_core::currency::Currency::USD: radroots_core::currency::Currency
-pub const fn radroots_core::currency::Currency::as_bytes(&self) -> &[u8; 3]
-pub fn radroots_core::currency::Currency::as_str(&self) -> &str
-pub const fn radroots_core::currency::Currency::from_const([u8; 3]) -> core::result::Result<Self, radroots_core::currency::ParseError>
-pub fn radroots_core::currency::Currency::from_str_upper(&str) -> core::result::Result<Self, radroots_core::currency::ParseError>
-pub const fn radroots_core::currency::Currency::minor_unit_exponent(&self) -> u32
-impl core::convert::TryFrom<&str> for radroots_core::currency::Currency
-pub type radroots_core::currency::Currency::Error = radroots_core::currency::ParseError
-pub fn radroots_core::currency::Currency::try_from(&str) -> core::result::Result<Self, Self::Error>
-impl core::fmt::Debug for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::fmt::Display for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::currency::Currency
-pub type radroots_core::currency::Currency::Err = radroots_core::currency::ParseError
-pub fn radroots_core::currency::Currency::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::currency::Currency
-pub fn radroots_core::currency::Currency::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub struct radroots_core::RadrootsCoreDecimal(pub rust_decimal::decimal::Decimal)
-impl radroots_core::decimal::Decimal
-pub const radroots_core::decimal::Decimal::MAX: Self
-pub const radroots_core::decimal::Decimal::MAX_SCALE: u32
-pub const radroots_core::decimal::Decimal::MIN: Self
-pub const radroots_core::decimal::Decimal::ONE: Self
-pub const radroots_core::decimal::Decimal::ZERO: Self
-pub fn radroots_core::decimal::Decimal::checked_add(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_div(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_mul(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::checked_sub(self, Self) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::from_f64_display(f64) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::from_str_exact(&str) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::decimal::Decimal::is_sign_negative(&self) -> bool
-pub fn radroots_core::decimal::Decimal::is_zero(&self) -> bool
-pub fn radroots_core::decimal::Decimal::normalize(&self) -> rust_decimal::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::rescale(&mut self, u32)
-pub fn radroots_core::decimal::Decimal::scale(&self) -> u32
-pub fn radroots_core::decimal::Decimal::to_f64_lossy(&self) -> core::option::Option<f64>
-pub fn radroots_core::decimal::Decimal::to_u64_exact(&self) -> core::option::Option<u64>
-pub fn radroots_core::decimal::Decimal::try_rescale_exact(&mut self, u32) -> core::result::Result<(), radroots_core::decimal::Error>
-impl core::convert::From<i32> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(i32) -> Self
-impl core::convert::From<i64> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(i64) -> Self
-impl core::convert::From<radroots_core::decimal::Decimal> for rust_decimal::decimal::Decimal
-pub fn rust_decimal::decimal::Decimal::from(radroots_core::decimal::Decimal) -> Self
-impl core::convert::From<rust_decimal::decimal::Decimal> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(rust_decimal::decimal::Decimal) -> Self
-impl core::convert::From<u32> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(u32) -> Self
-impl core::convert::From<u64> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::from(u64) -> Self
-impl core::fmt::Display for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Add for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::add(self, Self) -> Self
-impl core::ops::arith::Div for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::div(self, Self) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::mul(self, Self) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Sub for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Output = radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::sub(self, Self) -> Self
-impl core::str::traits::FromStr for radroots_core::decimal::Decimal
-pub type radroots_core::decimal::Decimal::Err = radroots_core::decimal::Error
-pub fn radroots_core::decimal::Decimal::from_str(&str) -> core::result::Result<Self, Self::Err>
-impl serde_core::ser::Serialize for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::serialize<S: serde_core::ser::Serializer>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error>
-impl<'de> serde_core::de::Deserialize<'de> for radroots_core::decimal::Decimal
-pub fn radroots_core::decimal::Decimal::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
-pub struct radroots_core::RadrootsCoreDiscount
-pub radroots_core::RadrootsCoreDiscount::scope: radroots_core::RadrootsCoreDiscountScope
-pub radroots_core::RadrootsCoreDiscount::threshold: radroots_core::RadrootsCoreDiscountThreshold
-pub radroots_core::RadrootsCoreDiscount::value: radroots_core::RadrootsCoreDiscountValue
-impl radroots_core::RadrootsCoreDiscount
-pub fn radroots_core::RadrootsCoreDiscount::is_non_negative(&self) -> bool
-pub fn radroots_core::RadrootsCoreDiscount::scope(&self) -> &radroots_core::RadrootsCoreDiscountScope
-pub fn radroots_core::RadrootsCoreDiscount::threshold(&self) -> &radroots_core::RadrootsCoreDiscountThreshold
-pub fn radroots_core::RadrootsCoreDiscount::try_new(radroots_core::RadrootsCoreDiscountScope, radroots_core::RadrootsCoreDiscountThreshold, radroots_core::RadrootsCoreDiscountValue) -> core::result::Result<Self, radroots_core::pricing::DiscountError>
-pub fn radroots_core::RadrootsCoreDiscount::validate(&self) -> core::result::Result<(), radroots_core::pricing::DiscountError>
-pub fn radroots_core::RadrootsCoreDiscount::value(&self) -> &radroots_core::RadrootsCoreDiscountValue
-pub struct radroots_core::RadrootsCoreMoney
-pub radroots_core::RadrootsCoreMoney::amount: radroots_core::decimal::Decimal
-pub radroots_core::RadrootsCoreMoney::currency: radroots_core::currency::Currency
-impl radroots_core::money::Money
-pub const fn radroots_core::money::Money::amount(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::money::Money::checked_add(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_div_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::checked_sub(&self, &Self) -> core::result::Result<Self, radroots_core::money::Error>
-pub const fn radroots_core::money::Money::currency(&self) -> radroots_core::currency::Currency
-pub fn radroots_core::money::Money::div_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::money::Error>
-pub fn radroots_core::money::Money::from_minor_units_u32(u32, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::from_minor_units_u64(u64, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::is_zero(&self) -> bool
-pub fn radroots_core::money::Money::mul_decimal(&self, radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::money::Money::new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency(self) -> Self
-pub fn radroots_core::money::Money::quantize_to_currency_with_strategy(self, rust_decimal::decimal::RoundingStrategy) -> Self
-pub fn radroots_core::money::Money::to_minor_units_u32_exact(&self) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u32_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u32, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_exact(&self) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::to_minor_units_u64_rounded(&self, rust_decimal::decimal::RoundingStrategy) -> core::result::Result<u64, radroots_core::money::Error>
-pub fn radroots_core::money::Money::try_new(radroots_core::decimal::Decimal, radroots_core::currency::Currency) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::try_with_scale_exact(self, u32) -> core::result::Result<Self, radroots_core::money::Error>
-pub fn radroots_core::money::Money::with_scale(self, u32) -> Self
-pub fn radroots_core::money::Money::zero(radroots_core::currency::Currency) -> Self
-impl core::fmt::Display for radroots_core::money::Money
-pub fn radroots_core::money::Money::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::div(self, radroots_core::decimal::Decimal) -> Self
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::money::Money
-pub type radroots_core::money::Money::Output = radroots_core::money::Money
-pub fn radroots_core::money::Money::mul(self, radroots_core::decimal::Decimal) -> Self
-pub struct radroots_core::RadrootsCorePercent
-pub radroots_core::RadrootsCorePercent::value: radroots_core::decimal::Decimal
-impl radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::from_ratio(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::new(radroots_core::decimal::Decimal) -> Self
-pub fn radroots_core::percent::Percent::of_money(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::of_money_quantized(&self, &radroots_core::money::Money) -> radroots_core::money::Money
-pub fn radroots_core::percent::Percent::to_ratio(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::percent::Percent::try_from_ratio(radroots_core::decimal::Decimal) -> core::result::Result<Self, radroots_core::decimal::Error>
-pub fn radroots_core::percent::Percent::try_of_money(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
-pub fn radroots_core::percent::Percent::try_of_money_quantized(&self, &radroots_core::money::Money) -> core::result::Result<radroots_core::money::Money, radroots_core::money::Error>
-pub fn radroots_core::percent::Percent::try_to_ratio(&self) -> core::result::Result<radroots_core::decimal::Decimal, radroots_core::decimal::Error>
-impl core::fmt::Display for radroots_core::percent::Percent
-pub fn radroots_core::percent::Percent::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::str::traits::FromStr for radroots_core::percent::Percent
-pub type radroots_core::percent::Percent::Err = radroots_core::percent::ParseError
-pub fn radroots_core::percent::Percent::from_str(&str) -> core::result::Result<Self, Self::Err>
-pub struct radroots_core::RadrootsCoreQuantity
-pub radroots_core::RadrootsCoreQuantity::amount: radroots_core::decimal::Decimal
-pub radroots_core::RadrootsCoreQuantity::label: core::option::Option<alloc::string::String>
-pub radroots_core::RadrootsCoreQuantity::unit: radroots_core::unit::Unit
-impl radroots_core::quantity::Quantity
-pub const fn radroots_core::quantity::Quantity::amount(&self) -> radroots_core::decimal::Decimal
-pub fn radroots_core::quantity::Quantity::canonical_unit(&self) -> radroots_core::unit::Unit
-pub fn radroots_core::quantity::Quantity::checked_add(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
-pub fn radroots_core::quantity::Quantity::checked_div_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::checked_mul_decimal(&self, radroots_core::decimal::Decimal) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::checked_sub(&self, &radroots_core::quantity::Quantity) -> core::option::Option<radroots_core::quantity::Quantity>
-pub fn radroots_core::quantity::Quantity::clear_label(self) -> Self
-pub fn radroots_core::quantity::Quantity::div_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::ensure_non_negative(&self) -> core::result::Result<(), radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::is_canonical(&self) -> bool
-pub fn radroots_core::quantity::Quantity::is_zero(&self) -> bool
-pub fn radroots_core::quantity::Quantity::label(&self) -> core::option::Option<&str>
-pub fn radroots_core::quantity::Quantity::mul_decimal(&self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> Self
-pub fn radroots_core::quantity::Quantity::to_canonical(&self) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
-pub fn radroots_core::quantity::Quantity::try_add(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::try_convert_to(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::unit::ConvertError>
-pub fn radroots_core::quantity::Quantity::try_new(radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<Self, radroots_core::quantity::Error>
-pub fn radroots_core::quantity::Quantity::try_sub(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::quantity::Quantity, radroots_core::quantity::Error>
-pub const fn radroots_core::quantity::Quantity::unit(&self) -> radroots_core::unit::Unit
-pub fn radroots_core::quantity::Quantity::with_label<S: core::convert::Into<alloc::string::String>>(self, S) -> Self
-pub fn radroots_core::quantity::Quantity::with_optional_label<S: core::convert::Into<alloc::string::String>>(self, core::option::Option<S>) -> Self
-pub fn radroots_core::quantity::Quantity::with_scale(self, u32) -> Self
-pub fn radroots_core::quantity::Quantity::zero(radroots_core::unit::Unit) -> Self
-impl core::fmt::Display for radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl core::ops::arith::Div<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::div(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-impl core::ops::arith::Mul<radroots_core::decimal::Decimal> for radroots_core::quantity::Quantity
-pub type radroots_core::quantity::Quantity::Output = radroots_core::quantity::Quantity
-pub fn radroots_core::quantity::Quantity::mul(self, radroots_core::decimal::Decimal) -> radroots_core::quantity::Quantity
-pub struct radroots_core::RadrootsCoreQuantityPrice
-pub radroots_core::RadrootsCoreQuantityPrice::amount: radroots_core::money::Money
-pub radroots_core::RadrootsCoreQuantityPrice::quantity: radroots_core::quantity::Quantity
impl radroots_core::QuantityPrice
pub fn radroots_core::QuantityPrice::amount(&self) -> &radroots_core::money::Money
pub fn radroots_core::QuantityPrice::is_price_per_canonical_unit(&self) -> bool
-pub fn radroots_core::QuantityPrice::new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> Self
pub fn radroots_core::QuantityPrice::quantity(&self) -> &radroots_core::quantity::Quantity
-pub fn radroots_core::QuantityPrice::try_cost_for_amount_in(&self, radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_quantity_in(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> core::result::Result<Self, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_canonical_unit_price(&self) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_to_unit_price(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::validate(&self) -> core::result::Result<(), radroots_core::RadrootsCoreQuantityPriceError>
-impl radroots_core::RadrootsCoreQuantityPriceOps for radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub trait radroots_core::RadrootsCoreQuantityPriceOps: radroots_core::quantity_price::sealed::Sealed
-pub fn radroots_core::RadrootsCoreQuantityPriceOps::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::RadrootsCoreQuantityPriceOps::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::RadrootsCoreQuantityPriceOps::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::RadrootsCoreQuantityPriceOps::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::RadrootsCoreQuantityPriceOps::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-impl radroots_core::RadrootsCoreQuantityPriceOps for radroots_core::QuantityPrice
-pub fn radroots_core::QuantityPrice::cost_for(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::cost_for_with_quantized_price(&self, &radroots_core::quantity::Quantity) -> radroots_core::money::Money
-pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
-pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::RadrootsCoreQuantityPriceError>
+pub fn radroots_core::QuantityPrice::try_cost_for_amount_in(&self, radroots_core::decimal::Decimal, radroots_core::unit::Unit) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_cost_for_quantity_in(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_new(radroots_core::money::Money, radroots_core::quantity::Quantity) -> core::result::Result<Self, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_to_canonical_unit_price(&self) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_to_unit_price(&self, radroots_core::unit::Unit) -> core::result::Result<radroots_core::QuantityPrice, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::validate(&self) -> core::result::Result<(), radroots_core::pricing::Error>
+impl radroots_core::pricing::QuantityPriceOps for radroots_core::QuantityPrice
+pub fn radroots_core::QuantityPrice::try_cost_for(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+pub fn radroots_core::QuantityPrice::try_cost_for_rounded(&self, &radroots_core::quantity::Quantity) -> core::result::Result<radroots_core::money::Money, radroots_core::pricing::Error>
+impl<'de> serde_core::de::Deserialize<'de> for radroots_core::QuantityPrice
+pub fn radroots_core::QuantityPrice::deserialize<D: serde_core::de::Deserializer<'de>>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error>
diff --git a/tools/xtask/src/architecture/core_contract.rs b/tools/xtask/src/architecture/core_contract.rs
@@ -22,68 +22,6 @@ const CORE_ROOT_EXPORTS: [(&str, &str); 8] = [
("QuantityPrice", "pricing::QuantityPrice"),
("Unit", "unit::Unit"),
];
-const TEMPORARY_ROOT_EXPORTS: [(&str, &str); 20] = [
- ("RadrootsCoreCurrency", "currency::RadrootsCoreCurrency"),
- (
- "RadrootsCoreCurrencyParseError",
- "currency::RadrootsCoreCurrencyParseError",
- ),
- ("RadrootsCoreDecimal", "decimal::RadrootsCoreDecimal"),
- ("RadrootsCoreDiscount", "discount::RadrootsCoreDiscount"),
- (
- "RadrootsCoreDiscountScope",
- "discount::RadrootsCoreDiscountScope",
- ),
- (
- "RadrootsCoreDiscountThreshold",
- "discount::RadrootsCoreDiscountThreshold",
- ),
- (
- "RadrootsCoreDiscountValue",
- "discount::RadrootsCoreDiscountValue",
- ),
- ("RadrootsCoreMoney", "money::RadrootsCoreMoney"),
- (
- "RadrootsCoreMoneyInvariantError",
- "money::RadrootsCoreMoneyInvariantError",
- ),
- ("RadrootsCorePercent", "percent::RadrootsCorePercent"),
- (
- "RadrootsCorePercentParseError",
- "percent::RadrootsCorePercentParseError",
- ),
- ("RadrootsCoreQuantity", "quantity::RadrootsCoreQuantity"),
- (
- "RadrootsCoreQuantityInvariantError",
- "quantity::RadrootsCoreQuantityInvariantError",
- ),
- (
- "RadrootsCoreQuantityPrice",
- "quantity_price::RadrootsCoreQuantityPrice",
- ),
- (
- "RadrootsCoreQuantityPriceError",
- "quantity_price::RadrootsCoreQuantityPriceError",
- ),
- (
- "RadrootsCoreQuantityPriceOps",
- "quantity_price::RadrootsCoreQuantityPriceOps",
- ),
- ("RadrootsCoreUnit", "unit::RadrootsCoreUnit"),
- (
- "RadrootsCoreUnitConvertError",
- "unit::RadrootsCoreUnitConvertError",
- ),
- (
- "RadrootsCoreUnitDimension",
- "unit::RadrootsCoreUnitDimension",
- ),
- (
- "RadrootsCoreUnitParseError",
- "unit::RadrootsCoreUnitParseError",
- ),
-];
-
pub(super) fn validate(workspace_root: &Path) -> Result<(), String> {
let architecture = read_toml(workspace_root, ARCHITECTURE_RELATIVE)?;
let core_spec = architecture
@@ -267,12 +205,11 @@ fn validate_crate_root(
}
let expected_exports = CORE_ROOT_EXPORTS
.into_iter()
- .chain(TEMPORARY_ROOT_EXPORTS)
.map(|(name, source)| (name.to_owned(), source.to_owned()))
.collect::<BTreeMap<_, _>>();
if exports != expected_exports {
return Err(format!(
- "radroots-core root exports must be the canonical contract plus the Step 034 compatibility set; expected {expected_exports:?}, found {exports:?}"
+ "radroots-core root exports must match the canonical contract; expected {expected_exports:?}, found {exports:?}"
));
}
Ok(())
@@ -420,7 +357,7 @@ fn collect_use_exports(
};
insert_export(exports, export_name, source)?;
}
- UseTree::Rename(rename) if rename.rename.to_string() != "_" => {
+ UseTree::Rename(rename) if rename.rename != "_" => {
insert_export(
exports,
rename.rename.to_string(),
@@ -559,7 +496,6 @@ serde_json = { workspace = true }
fs::write(root.path().join("crates/core/Cargo.toml"), MANIFEST).expect("core manifest");
let exports = super::CORE_ROOT_EXPORTS
.into_iter()
- .chain(super::TEMPORARY_ROOT_EXPORTS)
.map(|(_, source)| format!("pub use {source};"))
.collect::<Vec<_>>()
.join(" ");
@@ -627,7 +563,7 @@ serde_json = { workspace = true }
fs::write(&lib_path, format!("{source} pub use fixture::Clock;"))
.expect("extra export source");
let error = validate(root.path()).expect_err("extra export must fail");
- assert!(error.contains("root exports must be"), "{error}");
+ assert!(error.contains("root exports must match"), "{error}");
}
#[test]
@@ -641,7 +577,7 @@ serde_json = { workspace = true }
)
.expect("rebound export source");
let error = validate(root.path()).expect_err("rebound export must fail");
- assert!(error.contains("root exports must be"), "{error}");
+ assert!(error.contains("root exports must match"), "{error}");
fs::write(&lib_path, format!("{source} pub use currency::*;")).expect("glob export source");
let error = validate(root.path()).expect_err("glob export must fail");