lib

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

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:
Mcrates/core/README.md | 11++++++-----
Mcrates/core/src/currency.rs | 6------
Mcrates/core/src/decimal.rs | 59+++++++++++++++--------------------------------------------
Mcrates/core/src/discount.rs | 36++++++++++++++++++++----------------
Mcrates/core/src/lib.rs | 33---------------------------------
Mcrates/core/src/money.rs | 142+++++++++++++++++++++++++++++--------------------------------------------------
Mcrates/core/src/percent.rs | 29+++--------------------------
Mcrates/core/src/quantity.rs | 94+++++++++++++++++--------------------------------------------------------------
Mcrates/core/src/quantity_price.rs | 128+++++++++++++++++++++++--------------------------------------------------------
Mcrates/core/src/unit.rs | 12------------
Mcrates/core/tests/common/mod.rs | 4++--
Mcrates/core/tests/decimal.rs | 7+------
Mcrates/core/tests/discount.rs | 71+++++++----------------------------------------------------------------
Mcrates/core/tests/money.rs | 233++++++++++++++++++-------------------------------------------------------------
Mcrates/core/tests/package_boundary.rs | 25+++++--------------------
Mcrates/core/tests/percent.rs | 46++++++++++++++--------------------------------
Mcrates/core/tests/quantity.rs | 254++++++++++++++++++++++---------------------------------------------------------
Mcrates/core/tests/quantity_price.rs | 236++++++++++++++++++++++---------------------------------------------------------
Mcrates/core/tests/serde.rs | 38+++++++++++++++++++++++++++++++++-----
Mcrates/event/src/order.rs | 85++++++++++++++++++++++++++++---------------------------------------------------
Mcrates/event_codec/src/farm/mod.rs | 11++++++-----
Mcrates/event_codec/src/operational_listing/decode.rs | 22+++++++++++++++-------
Mcrates/event_codec/src/operational_listing/encode.rs | 11++++++-----
Mcrates/event_codec/src/operational_listing/tags.rs | 114+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcrates/event_codec/src/order/decode.rs | 2+-
Mcrates/event_codec/src/resource_cap/encode.rs | 2+-
Mcrates/event_codec/src/resource_cap/mod.rs | 4++--
Mcrates/event_codec/tests/domain_encode_non_serde.rs | 53+++++++++++++++++++++++++++++++----------------------
Mcrates/event_codec/tests/operational_listing.rs | 81++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mcrates/event_codec/tests/structured_decode.rs | 2+-
Mcrates/event_codec/tests/structured_encode_default.rs | 15++++++++-------
Mcrates/event_codec/tests/tag_builders.rs | 30+++++++++++++++++-------------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.json | 28++++++++++++++--------------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.sha256 | 2+-
Mcrates/event_store/src/generated/source_maintenance_manifest.rs | 4++--
Mcrates/replica_sync/src/ingest.rs | 13++++++++-----
Mcrates/trade/src/operational_listing/draft.rs | 19++++++++++++-------
Mcrates/trade/src/operational_listing/mutation.rs | 11++++++-----
Mcrates/trade/src/operational_listing/price_ext.rs | 98+++++++++++++++++++++++++++++++------------------------------------------------
Mcrates/trade/src/operational_listing/validation.rs | 123++++++++++++++++++++++++++++++-------------------------------------------------
Mdocs/api/radroots-core.txt | 883++++++-------------------------------------------------------------------------
Mtools/xtask/src/architecture/core_contract.rs | 72++++--------------------------------------------------------------------
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");