Add english variant of decimal parser
Dependencies
- [2]
AQ6A6PSUHide unnecessary public method - [3]
CFT26V62Improve proptest for german-decimal - [4]
5XUAFMCKEnforce decimal precision for ebase importer - [5]
J7MCZEIRFix clippy warnings in generators - [6]
DWUVE75BHandle foreign currency purchases in DKB importer - [7]
BDLVPDJZAdd a importer account for BW bank portfolios - [8]
D6LJRTWXAdd importers for ebase accounts - [9]
OAOH3QOZExtract metadata into separate crate - [10]
MDRERLJHUpgrade dependencies - [11]
362NCCMXAdd importer for Apple Store - [12]
XQHYMSDYAdd importer for Union Investment transactions - [13]
A35XMWPSRestructure workspace - [14]
6A5YLGWVAdd an importer for the VR-Bank CSV format - [15]
U5SMZFJXUpdate dependencies - [16]
E2DDQ4MXDefine inheritable lints configuration - [17]
PCHAKXNMAdd an importer for Fidor account statements - [18]
D6UTHZA4add a simple writer for saving a set of directives to a tree of files - [19]
RCS5VP3AAdd an importer for PayPal account statements - [20]
YDK6X6PPadd a library of important types for beancount - [21]
YNK6C2REAdd importer for CAMT exports by BW Bank - [22]
I2P2FTLEadd basic parser for german decimals - [23]
GUNQ3BZUAdd alias for special type of credit - [24]
4WYI5U7YUpgrade dependencies - [25]
A46ZUUSWRecord effective dates for card transactions in DKB importer - [26]
PWHXDFPWRename importer - [27]
QRIJE4AQadd a simple pretty printer for beancount directives - [28]
7URE4HPLAdd an importer for DKB account statements - [29]
2JBFREZGenable additional warnings - [30]
7VFBUJ6Padd basic property test for decimal parser - [31]
PUM5OY34Remove unused dependencies - [32]
UO34MAAGRefactor CSV-based Importers - [*]
UESS5YZEmigrate dependencies into workspace manifest - [*]
ILUKKAPNImplement Arbitrary for core data types behind a feature flag
Change contents
- replacement in importers/vr-bank/src/lib.rs at line 254
#[serde(rename = "Betrag", with = "german_decimal::serde")]#[serde(rename = "Betrag", with = "decimal_parsers::german::serde")] - replacement in importers/vr-bank/src/lib.rs at line 257
#[serde(rename = "Saldo nach Buchung", with = "german_decimal::serde")]#[serde(rename = "Saldo nach Buchung", with = "decimal_parsers::german::serde")] - replacement in importers/vr-bank/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/uniondepot/src/lib.rs at line 352
#[serde(rename = "FONDSPREIS", with = "german_decimal::serde")]#[serde(rename = "FONDSPREIS", with = "decimal_parsers::german::serde")] - replacement in importers/uniondepot/src/lib.rs at line 355
#[serde(rename = "ANTEILE", with = "german_decimal::serde")]#[serde(rename = "ANTEILE", with = "decimal_parsers::german::serde")] - replacement in importers/uniondepot/src/lib.rs at line 358
#[serde(rename = "VOLUMEN", with = "german_decimal::serde")]#[serde(rename = "VOLUMEN", with = "decimal_parsers::german::serde")] - replacement in importers/uniondepot/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/paypal/src/lib.rs at line 375
#[serde(rename = "Brutto", with = "german_decimal::serde")]#[serde(rename = "Brutto", with = "decimal_parsers::german::serde")] - replacement in importers/paypal/src/lib.rs at line 378
#[serde(rename = "Guthaben", with = "german_decimal::serde")]#[serde(rename = "Guthaben", with = "decimal_parsers::german::serde")] - replacement in importers/paypal/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/fidor/src/lib.rs at line 377
#[serde(rename = "Wert", with = "german_decimal::serde")]#[serde(rename = "Wert", with = "decimal_parsers::german::serde")] - replacement in importers/fidor/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/ebase/src/transactions.rs at line 571
with = "german_decimal::serde::opt::with_precision::<6>"with = "decimal_parsers::german::serde::opt::with_precision::<6>" - replacement in importers/ebase/src/transactions.rs at line 577
with = "german_decimal::serde::with_precision::<2>"with = "decimal_parsers::german::serde::with_precision::<2>" - replacement in importers/ebase/src/transactions.rs at line 589
with = "german_decimal::serde::with_precision::<2>"with = "decimal_parsers::german::serde::with_precision::<2>" - replacement in importers/ebase/src/transactions.rs at line 598
with = "german_decimal::serde::with_precision::<2>"with = "decimal_parsers::german::serde::with_precision::<2>" - replacement in importers/ebase/src/transactions.rs at line 613
with = "german_decimal::serde::with_precision::<6>"with = "decimal_parsers::german::serde::with_precision::<6>" - replacement in importers/ebase/src/transactions.rs at line 619
with = "german_decimal::serde::with_precision::<6>"with = "decimal_parsers::german::serde::with_precision::<6>" - replacement in importers/ebase/src/balances.rs at line 237
#[serde(rename = "Anteile", with = "german_decimal::serde")]#[serde(rename = "Anteile", with = "decimal_parsers::german::serde")] - replacement in importers/ebase/src/balances.rs at line 240
#[serde(rename = "Anteilswert", with = "german_decimal::serde")]#[serde(rename = "Anteilswert", with = "decimal_parsers::german::serde")] - replacement in importers/ebase/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/dkb/src/lib.rs at line 544
#[serde(rename = "Betrag (EUR)", with = "german_decimal::serde::opt")]#[serde(rename = "Betrag (EUR)", with = "decimal_parsers::german::serde::opt")] - replacement in importers/dkb/src/lib.rs at line 717
let amount = german_decimal::parse(amount).map_err(|_| todo!())?;let amount = decimal_parsers::german::parse(amount).map_err(|_| todo!())?; - replacement in importers/dkb/Cargo.toml at line 14
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - replacement in importers/bw-bank/src/portfolio.rs at line 247
#[serde(rename = "Stück", with = "german_decimal::serde")]#[serde(rename = "Stück", with = "decimal_parsers::german::serde")] - replacement in importers/bw-bank/src/portfolio.rs at line 366
let amount = german_decimal::parse(amount).map_err(E::custom)?;let amount = decimal_parsers::german::parse(amount).map_err(E::custom)?; - edit in importers/bw-bank/Cargo.toml at line 15
decimal-parsers.workspace = true - edit in importers/bw-bank/Cargo.toml at line 17
german-decimal.path = "../../common/german-decimal" - replacement in importers/apple/src/transaction_history.rs at line 358
german_decimal::parse(v).map_err(E::custom)decimal_parsers::german::parse(v).map_err(E::custom) - replacement in importers/apple/Cargo.toml at line 15
german-decimal.path = "../../common/german-decimal"decimal-parsers.workspace = true - file move: german-decimal → decimal-parsers
- replacement in common/decimal-parsers/tests/properties/main.rs at line 6
fn can_parse_arbitrary_localized_decimal(decimal in r"(-|\+)?[0-9]{1,3}(\.[0-9]{3}){0,3}(,[0-9]{1,10})?") {let result = german_decimal::parse(&decimal);fn can_parse_arbitrary_german_decimal(decimal in r"(-|\+)?[0-9]{1,3}(\.[0-9]{3}){0,3}(,[0-9]{1,10})?") {let result = decimal_parsers::german::parse(&decimal);if let Err(message) = result {eprintln!("{message}");prop_assert!(false);}}#[test]fn can_parse_arbitrary_english_decimal(decimal in r"(-|\+)?[0-9]{1,3}(,[0-9]{3}){0,3}(\.[0-9]{1,10})?") {let result = decimal_parsers::english::parse(&decimal); - edit in common/decimal-parsers/tests/properties/main.rs at line 23[6.423]
// TODO more elaborate testing using decimal formatter - replacement in common/decimal-parsers/tests/basic.rs at line 1
use rust_decimal_macros::dec;mod german {use decimal_parsers::german as german_decimal;use rust_decimal_macros::dec; - replacement in common/decimal-parsers/tests/basic.rs at line 5
#[test]fn accepts_integer() {assert_eq!(german_decimal::parse("1").unwrap(), dec!(1))}#[test]fn accepts_integer() {assert_eq!(german_decimal::parse("1").unwrap(), dec!(1));} - replacement in common/decimal-parsers/tests/basic.rs at line 10
#[test]fn accepts_decimals() {let parsed = german_decimal::parse("0,3").unwrap();assert_eq!(parsed, dec!(0.3))}#[test]fn accepts_decimals() {let parsed = german_decimal::parse("0,3").unwrap();assert_eq!(parsed, dec!(0.3));} - replacement in common/decimal-parsers/tests/basic.rs at line 16
#[test]fn accepts_group_seperators() {let parsed = german_decimal::parse("2.500").unwrap();assert_eq!(parsed, dec!(2500))}#[test]fn accepts_group_seperators() {let parsed = german_decimal::parse("2.500").unwrap();assert_eq!(parsed, dec!(2500));} - replacement in common/decimal-parsers/tests/basic.rs at line 22
#[test]fn accepts_group_seperators_and_decimals() {let parsed = german_decimal::parse("2.500,12345").unwrap();assert_eq!(parsed, dec!(2500.12345))}#[test]fn accepts_group_seperators_and_decimals() {let parsed = german_decimal::parse("2.500,12345").unwrap();assert_eq!(parsed, dec!(2500.12345));} - replacement in common/decimal-parsers/tests/basic.rs at line 28
#[test]fn accepts_minus_sign() {assert_eq!(german_decimal::parse("-2.500,12345").unwrap(),dec!(-2500.12345));}#[test]fn accepts_minus_sign() {assert_eq!(german_decimal::parse("-2.500,12345").unwrap(),dec!(-2500.12345));} - replacement in common/decimal-parsers/tests/basic.rs at line 36
#[test]fn accepts_plus_sign() {assert_eq!(german_decimal::parse("+2.500,12345").unwrap(),dec!(2500.12345));}#[test]fn accepts_plus_sign() {assert_eq!(german_decimal::parse("+2.500,12345").unwrap(),dec!(2500.12345));} - replacement in common/decimal-parsers/tests/basic.rs at line 44
#[test]fn rejects_empty_numbers() {german_decimal::parse("").unwrap_err();}#[test]fn rejects_empty_numbers() {german_decimal::parse("").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 49
#[test]fn rejects_leading_separator() {german_decimal::parse(".500,12345").unwrap_err();}#[test]fn rejects_leading_separator() {german_decimal::parse(".500,12345").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 54
#[test]fn rejects_only_decimal_separator() {german_decimal::parse(",").unwrap_err();}#[test]fn rejects_only_decimal_separator() {german_decimal::parse(",").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 59
#[test]fn rejects_only_plus_sign() {german_decimal::parse("+").unwrap_err();}#[test]fn rejects_only_plus_sign() {german_decimal::parse("+").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 64
#[test]fn rejects_only_minus_sign() {german_decimal::parse("-").unwrap_err();}#[test]fn rejects_only_minus_sign() {german_decimal::parse("-").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 69
#[test]fn rejects_interior_minus_sign() {german_decimal::parse("12-345").unwrap_err();}#[test]fn rejects_interior_minus_sign() {german_decimal::parse("12-345").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 74
#[test]fn rejects_interior_plus_sign() {german_decimal::parse("12+345").unwrap_err();}#[test]fn rejects_interior_plus_sign() {german_decimal::parse("12+345").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 79
#[test]fn rejects_letters() {german_decimal::parse("a").unwrap_err();}#[test]fn rejects_letters() {german_decimal::parse("a").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 84
#[test]fn rejects_symbols() {german_decimal::parse("_").unwrap_err();}#[test]fn rejects_symbols() {german_decimal::parse("_").unwrap_err();} - replacement in common/decimal-parsers/tests/basic.rs at line 89
#[test]fn rejects_group_separators_in_decimals() {german_decimal::parse("2.500,123.456").unwrap_err();#[test]fn rejects_group_separators_in_decimals() {german_decimal::parse("2.500,123.456").unwrap_err();} - file deletion: serde.rs
use rust_decimal::Decimal;pub mod opt {use rust_decimal::Decimal;#[allow(non_camel_case_types)]pub struct with_precision<const PRECISION: u32>;impl<const PRECISION: u32> with_precision<PRECISION> {pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor<const PRECISION: u32>;impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {type Value = Option<Decimal>;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("an optional decimal number in german format")}fn visit_none<E>(self) -> Result<Self::Value, E>whereE: serde::de::Error,{Ok(None)}fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>whereD: serde::Deserializer<'de>,{super::with_precision::<PRECISION>::deserialize(deserializer).map(Some)}}deserializer.deserialize_option(Visitor::<PRECISION>)}}pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor;impl<'de> serde::de::Visitor<'de> for Visitor {type Value = Option<Decimal>;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("an optional decimal number in german format")}fn visit_none<E>(self) -> Result<Self::Value, E>whereE: serde::de::Error,{Ok(None)}fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>whereD: serde::Deserializer<'de>,{super::deserialize(deserializer).map(Some)}}deserializer.deserialize_option(Visitor)}}#[allow(non_camel_case_types)]pub struct with_precision<const PRECISION: u32>;impl<const PRECISION: u32> with_precision<PRECISION> {pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor<const PRECISION: u32>;impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {type Value = Decimal;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("a decimal number in german format")}fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>whereE: serde::de::Error,{let mut amount = crate::parse(v).map_err(E::custom)?;amount.rescale(PRECISION);Ok(amount)}}deserializer.deserialize_str(Visitor::<PRECISION>)}}pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor;impl<'de> serde::de::Visitor<'de> for Visitor {type Value = Decimal;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("a decimal number in german format")}fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>whereE: serde::de::Error,{crate::parse(v).map_err(E::custom)}}deserializer.deserialize_str(Visitor)} - file move: lib.rs → lib.rs
- replacement in common/decimal-parsers/src/lib.rs at line 1
//! This is based on a copy of [`Decimal::from_str_exact`], with adaptations to handle german decimals.// TODO consider splitting this upmacro_rules! make_module {($name:ident, $decimal_separator:literal, $group_separator:literal) => {pub mod $name {# formatted with ",stringify!($name)," locale-specific grouping (`",stringify!($group_separator),"`) and decimal separators (`",stringify!($decimal_separator),"`)",)]use rust_decimal::Decimal;use rust_decimal::Error;const DECIMAL_SEPARATOR: u8 = $decimal_separator;const GROUP_SEPARATOR: u8 = $group_separator;// Determines potential overflow for 128 bit operationsconst OVERFLOW_U96: u128 = 1u128 << 96;const WILL_OVERFLOW_U64: u64 = u64::MAX / 10 - u8::MAX as u64;const BYTES_TO_OVERFLOW_U64: usize = 18; // We can probably get away with lesspub mod serde {//! This module contains a deserialize function to be used with [`serde`]'s `with` attribute to enable//! locale-specific deserialization of [`Decimal`].use rust_decimal::Decimal;pub mod opt {//! This module contains a deserialize function to be used with [`serde`]'s `with` attribute to//! enable locale-specific deserialization of [`Option<Decimal>`].use rust_decimal::Decimal;#[allow(non_camel_case_types)]/// An adapter to enforce a given precision while deserializing a locale-specific/// [`Option<Decimal>`].pub struct with_precision<const PRECISION: u32>;impl<const PRECISION: u32> with_precision<PRECISION> {/// Deserializes an [`Option<Decimal>`] in the locale-specific format and enforces a given/// precision.////// The precision is enforced using [`Decimal::rescale`].////// # Errors////// Returns an error if deserialization fails or the deserialized value cannot be parsed as a/// `Decimal` according to#[doc = concat!("[`crate::", stringify!($name), "::parse`].")]pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor<const PRECISION: u32>;impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {type Value = Option<Decimal>;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("an optional decimal number in german format")}fn visit_none<E>(self) -> Result<Self::Value, E>whereE: serde::de::Error,{Ok(None)}fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>whereD: serde::Deserializer<'de>,{super::with_precision::<PRECISION>::deserialize(deserializer).map(Some)}}deserializer.deserialize_option(Visitor::<PRECISION>)}}/// Deserializes an [`Option<Decimal>`] in the locale-specific format.////// # Errors////// Returns an error if deserialization fails or the deserialized value cannot be parsed as a/// `Decimal` according to#[doc = concat!("[`crate::", stringify!($name), "::parse`].")]pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor; - replacement in common/decimal-parsers/src/lib.rs at line 98
use rust_decimal::Decimal;use rust_decimal::Error;impl<'de> serde::de::Visitor<'de> for Visitor {type Value = Option<Decimal>;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("an optional decimal number in german format")}fn visit_none<E>(self) -> Result<Self::Value, E>whereE: serde::de::Error,{Ok(None)} - replacement in common/decimal-parsers/src/lib.rs at line 112
pub mod serde;fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>whereD: serde::Deserializer<'de>,{super::deserialize(deserializer).map(Some)}} - replacement in common/decimal-parsers/src/lib.rs at line 120
// Determines potential overflow for 128 bit operationsconst OVERFLOW_U96: u128 = 1u128 << 96;const WILL_OVERFLOW_U64: u64 = u64::MAX / 10 - u8::MAX as u64;const BYTES_TO_OVERFLOW_U64: usize = 18; // We can probably get away with lessdeserializer.deserialize_option(Visitor)}} - replacement in common/decimal-parsers/src/lib.rs at line 124
#[inline]pub fn parse(value: &str) -> Result<Decimal, Error> {let bytes = value.as_bytes();if bytes.len() < BYTES_TO_OVERFLOW_U64 {parse_str_radix_10_dispatch::<false>(bytes)} else {parse_str_radix_10_dispatch::<true>(bytes)}}#[allow(non_camel_case_types)]/// An adapter to enforce a given precision while deserializing a locale-specific [`Decimal`].pub struct with_precision<const PRECISION: u32>; - replacement in common/decimal-parsers/src/lib.rs at line 128
#[inline]fn parse_str_radix_10_dispatch<const BIG: bool>(bytes: &[u8]) -> Result<Decimal, Error> {match bytes {[b, rest @ ..] => byte_dispatch_u64::<false, false, false, BIG, true>(rest, 0, 0, *b),[] => tail_error("invalid decimal: empty"),}}impl<const PRECISION: u32> with_precision<PRECISION> {/// Deserializes a [`Decimal`] in the locale-specific format and enforces a given precision.////// The precision is enforced using [`Decimal::rescale`].////// # Errors////// Returns an error if deserialization fails or the deserialized value cannot be parsed as a/// `Decimal` according to#[doc = concat!("[`crate::", stringify!($name), "::parse`].")]pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor<const PRECISION: u32>; - replacement in common/decimal-parsers/src/lib.rs at line 144
#[inline]fn overflow_64(val: u64) -> bool {val >= WILL_OVERFLOW_U64}impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {type Value = Decimal; - replacement in common/decimal-parsers/src/lib.rs at line 147[6.2900]→[6.2900:2910](∅→∅),[6.2910]→[2.8:45](∅→∅),[2.45]→[6.2951:2977](∅→∅),[6.2951]→[6.2951:2977](∅→∅)
#[inline]fn overflow_128(val: u128) -> bool {val >= OVERFLOW_U96}fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("a decimal number in german format")} - replacement in common/decimal-parsers/src/lib.rs at line 151[6.2978]→[6.2978:3010](∅→∅),[6.3010]→[6.2919:3257](∅→∅),[6.3257]→[6.3338:3840](∅→∅),[6.3338]→[6.3338:3840](∅→∅)
/// Dispatch the next byte:////// * `SAW_DECIMAL_SEPARATOR` - a decimal point has been seen/// * `NEGATIVE` - we've encountered a `-` and the number is negative/// * `SAW_DIGIT` - a digit has been encountered (when HAS is false it's invalid)/// * `BIG` - a number that uses 96 bits instead of only 64 bits/// * `FIRST` - true if it is the first byte in the string#[inline]fn dispatch_next<const SAW_DECIMAL_SEPARATOR: bool,const NEGATIVE: bool,const SAW_DIGIT: bool,const BIG: bool,>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {if let Some((next, bytes)) = bytes.split_first() {byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, SAW_DIGIT, BIG, false>(bytes, data64, scale, *next,)} else {handle_data::<NEGATIVE, SAW_DIGIT>(data64 as u128, scale)}}fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>whereE: serde::de::Error,{let mut amount = super::parse(v).map_err(E::custom)?;amount.rescale(PRECISION);Ok(amount)}} - replacement in common/decimal-parsers/src/lib.rs at line 161
#[inline(never)]fn non_digit_dispatch_u64<const SAW_DECIMAL_SEPARATOR: bool,const NEG: bool,const NON_EMPTY: bool,const BIG: bool,const FIRST: bool,>(bytes: &[u8],data64: u64,scale: u8,b: u8,) -> Result<Decimal, Error> {match b {b'-' if FIRST && !NON_EMPTY => {dispatch_next::<false, true, false, BIG>(bytes, data64, scale)}b'+' if FIRST && !NON_EMPTY => {dispatch_next::<false, false, false, BIG>(bytes, data64, scale)}b'.' if !SAW_DECIMAL_SEPARATOR && NON_EMPTY => {handle_separator::<SAW_DECIMAL_SEPARATOR, NEG, BIG>(bytes, data64, scale)}b => tail_invalid_digit(b),}}deserializer.deserialize_str(Visitor::<PRECISION>)}} - replacement in common/decimal-parsers/src/lib.rs at line 165
#[inline]fn byte_dispatch_u64<const SAW_DECIMAL_SEPARATOR: bool,const NEGATIVE: bool,const NON_EMPTY: bool,const BIG: bool,const FIRST: bool,>(bytes: &[u8],data64: u64,scale: u8,b: u8,) -> Result<Decimal, Error> {match b {b'0'..=b'9' => {handle_digit_64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, BIG>(bytes, data64, scale, b - b'0')}b',' if !SAW_DECIMAL_SEPARATOR => {handle_point::<NEGATIVE, NON_EMPTY, BIG>(bytes, data64, scale)}b => non_digit_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, NON_EMPTY, BIG, FIRST>(bytes, data64, scale, b,),}}/// Deserializes a [`Decimal`] in the locale-specific format.////// # Errors////// Returns an error if deserialization fails or the deserialized value cannot be parsed as a `Decimal`/// according to#[doc = concat!("[`crate::", stringify!($name), "::parse`].")]pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>whereD: serde::Deserializer<'de>,{struct Visitor; - replacement in common/decimal-parsers/src/lib.rs at line 178
#[inline(never)]fn handle_digit_64<const SAW_DECIMAL_SEPARATOR: bool, const NEGATIVE: bool, const BIG: bool>(bytes: &[u8],data64: u64,scale: u8,digit: u8,) -> Result<Decimal, Error> {// we have already validated that we cannot overflowlet data64 = data64 * 10 + digit as u64;let scale = if SAW_DECIMAL_SEPARATOR { scale + 1 } else { 0 };impl<'de> serde::de::Visitor<'de> for Visitor {type Value = Decimal; - replacement in common/decimal-parsers/src/lib.rs at line 181
if let Some((next, bytes)) = bytes.split_first() {let next = *next;if SAW_DECIMAL_SEPARATOR && BIG && scale >= 28 {Err(Error::Underflow)} else if BIG && overflow_64(data64) {handle_full_128::<SAW_DECIMAL_SEPARATOR, NEGATIVE>(data64 as u128, bytes, scale, next)} else {byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, true, BIG, false>(bytes, data64, scale, next,)}} else {let data: u128 = data64 as u128;fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {formatter.write_str("a decimal number in german format")} - replacement in common/decimal-parsers/src/lib.rs at line 185
handle_data::<NEGATIVE, true>(data, scale)}}fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>whereE: serde::de::Error,{super::parse(v).map_err(E::custom)}} - replacement in common/decimal-parsers/src/lib.rs at line 193
#[inline(never)]fn handle_point<const NEG: bool, const NON_EMPTY: bool, const BIG: bool>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {dispatch_next::<true, NEG, NON_EMPTY, BIG>(bytes, data64, scale)}deserializer.deserialize_str(Visitor)}} - replacement in common/decimal-parsers/src/lib.rs at line 197
#[inline(never)]fn handle_separator<const SAW_DECIMAL_SEPARATOR: bool, const NEG: bool, const BIG: bool>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {dispatch_next::<SAW_DECIMAL_SEPARATOR, NEG, true, BIG>(bytes, data64, scale)}#[inline]#[doc = concat!("This function is based on [`Decimal::from_str_exact`], but adapted to use different grouping (`",stringify!($group_separator),"`) and decimal separators (`",stringify!($decimal_separator),"`)",)]/// # Errors////// Returns an error when the given string does not represent a valid `Decimal` or if the value cannot be/// represented with the supported precision, i.e., if it causes over- or underflow.pub fn parse(value: &str) -> Result<Decimal, Error> {let bytes = value.as_bytes();if bytes.len() < BYTES_TO_OVERFLOW_U64 {parse_str_radix_10_dispatch::<false>(bytes)} else {parse_str_radix_10_dispatch::<true>(bytes)}} - replacement in common/decimal-parsers/src/lib.rs at line 218
#[cold]fn tail_error(from: &'static str) -> Result<Decimal, Error> {Err(from.into())}#[inline]fn parse_str_radix_10_dispatch<const BIG: bool>(bytes: &[u8],) -> Result<Decimal, Error> {match bytes {[b, rest @ ..] => {byte_dispatch_u64::<false, false, false, BIG, true>(rest, 0, 0, *b)}[] => tail_error("invalid decimal: empty"),}} - replacement in common/decimal-parsers/src/lib.rs at line 230
#[inline(never)]#[cold]fn tail_invalid_digit(digit: u8) -> Result<Decimal, Error> {match digit {b',' => tail_error("invalid decimal: two decimal points"),// b'_' => tail_error("Invalid decimal: must start lead with a number"),_ => tail_error("invalid decimal: unknown character"),}}#[inline]const fn overflow_64(val: u64) -> bool {val >= WILL_OVERFLOW_U64} - replacement in common/decimal-parsers/src/lib.rs at line 235
#[inline(never)]#[cold]fn handle_full_128<const SAW_DECIMAL_SEPARATOR: bool, const NEG: bool>(mut data: u128,bytes: &[u8],scale: u8,next_byte: u8,) -> Result<Decimal, Error> {let b = next_byte;match b {b'0'..=b'9' => {let digit = u32::from(b - b'0');#[inline]const fn overflow_128(val: u128) -> bool {val >= OVERFLOW_U96} - replacement in common/decimal-parsers/src/lib.rs at line 240
// If the data is going to overflow then we should go into recovery modelet next = (data * 10) + digit as u128;if overflow_128(next) {if !SAW_DECIMAL_SEPARATOR {tail_error("invalid decimal: overflow from too many digits")/// Dispatch the next byte:////// * `SAW_DECIMAL_SEPARATOR` - a decimal point has been seen/// * `NEGATIVE` - we've encountered a `-` and the number is negative/// * `SAW_DIGIT` - a digit has been encountered (when HAS is false it's invalid)/// * `BIG` - a number that uses 96 bits instead of only 64 bits/// * `FIRST` - true if it is the first byte in the string#[inline]fn dispatch_next<const SAW_DECIMAL_SEPARATOR: bool,const NEGATIVE: bool,const SAW_DIGIT: bool,const BIG: bool,>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {if let Some((next, bytes)) = bytes.split_first() {byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, SAW_DIGIT, BIG, false>(bytes, data64, scale, *next,) - replacement in common/decimal-parsers/src/lib.rs at line 263
Err(Error::Underflow)handle_data::<NEGATIVE, SAW_DIGIT>(u128::from(data64), scale) - replacement in common/decimal-parsers/src/lib.rs at line 265
} else {data = next;let scale = scale + SAW_DECIMAL_SEPARATOR as u8;}#[inline(never)]fn non_digit_dispatch_u64<const SAW_DECIMAL_SEPARATOR: bool,const NEG: bool,const NON_EMPTY: bool,const BIG: bool,const FIRST: bool,>(bytes: &[u8],data64: u64,scale: u8,b: u8,) -> Result<Decimal, Error> {match b {b'-' if FIRST && !NON_EMPTY => {dispatch_next::<false, true, false, BIG>(bytes, data64, scale)}b'+' if FIRST && !NON_EMPTY => {dispatch_next::<false, false, false, BIG>(bytes, data64, scale)}GROUP_SEPARATOR if !SAW_DECIMAL_SEPARATOR && NON_EMPTY => {handle_separator::<SAW_DECIMAL_SEPARATOR, NEG, BIG>(bytes, data64, scale)}b => tail_invalid_digit(b),}}#[inline]fn byte_dispatch_u64<const SAW_DECIMAL_SEPARATOR: bool,const NEGATIVE: bool,const NON_EMPTY: bool,const BIG: bool,const FIRST: bool,>(bytes: &[u8],data64: u64,scale: u8,b: u8,) -> Result<Decimal, Error> {match b {b'0'..=b'9' => handle_digit_64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, BIG>(bytes,data64,scale,b - b'0',),DECIMAL_SEPARATOR if !SAW_DECIMAL_SEPARATOR => {handle_point::<NEGATIVE, NON_EMPTY, BIG>(bytes, data64, scale)}b => non_digit_dispatch_u64::<SAW_DECIMAL_SEPARATOR,NEGATIVE,NON_EMPTY,BIG,FIRST,>(bytes, data64, scale, b),}}#[inline(never)]fn handle_digit_64<const SAW_DECIMAL_SEPARATOR: bool,const NEGATIVE: bool,const BIG: bool,>(bytes: &[u8],data64: u64,scale: u8,digit: u8,) -> Result<Decimal, Error> {// we have already validated that we cannot overflowlet data64 = data64 * 10 + u64::from(digit);let scale = if SAW_DECIMAL_SEPARATOR { scale + 1 } else { 0 }; - replacement in common/decimal-parsers/src/lib.rs at line 344
if SAW_DECIMAL_SEPARATOR && scale >= 28 {if SAW_DECIMAL_SEPARATOR && BIG && scale >= 28 { - edit in common/decimal-parsers/src/lib.rs at line 346
} else if BIG && overflow_64(data64) {handle_full_128::<SAW_DECIMAL_SEPARATOR, NEGATIVE>(data64 as u128,bytes,scale,next,) - replacement in common/decimal-parsers/src/lib.rs at line 354
handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, next)byte_dispatch_u64::<SAW_DECIMAL_SEPARATOR, NEGATIVE, true, BIG, false>(bytes, data64, scale, next,) - replacement in common/decimal-parsers/src/lib.rs at line 359
handle_data::<NEG, true>(data, scale)let data: u128 = data64 as u128;handle_data::<NEGATIVE, true>(data, scale) - replacement in common/decimal-parsers/src/lib.rs at line 364
}b',' if !SAW_DECIMAL_SEPARATOR => {// This call won't tail?if let Some((next, bytes)) = bytes.split_first() {handle_full_128::<true, NEG>(data, bytes, scale, *next)} else {handle_data::<NEG, true>(data, scale)#[inline(never)]fn handle_point<const NEG: bool, const NON_EMPTY: bool, const BIG: bool>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {dispatch_next::<true, NEG, NON_EMPTY, BIG>(bytes, data64, scale) - replacement in common/decimal-parsers/src/lib.rs at line 373
}b'.' => {if let Some((next, bytes)) = bytes.split_first() {handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, *next)} else {handle_data::<NEG, true>(data, scale)#[inline(never)]fn handle_separator<const SAW_DECIMAL_SEPARATOR: bool,const NEG: bool,const BIG: bool,>(bytes: &[u8],data64: u64,scale: u8,) -> Result<Decimal, Error> {dispatch_next::<SAW_DECIMAL_SEPARATOR, NEG, true, BIG>(bytes, data64, scale) - edit in common/decimal-parsers/src/lib.rs at line 386
}b => tail_invalid_digit(b),}} - replacement in common/decimal-parsers/src/lib.rs at line 387
#[inline(never)]fn tail_empty() -> Result<Decimal, Error> {tail_error("invalid decimal: no digits found")}#[cold]fn tail_error(from: &'static str) -> Result<Decimal, Error> {Err(from.into())} - replacement in common/decimal-parsers/src/lib.rs at line 392
#[inline]fn handle_data<const NEG: bool, const HAS: bool>(data: u128, scale: u8) -> Result<Decimal, Error> {debug_assert_eq!(data >> 96, 0);#[inline(never)]#[cold]fn tail_invalid_digit(digit: u8) -> Result<Decimal, Error> {match digit {DECIMAL_SEPARATOR => tail_error("invalid decimal: two decimal points"),// b'_' => tail_error("Invalid decimal: must start lead with a number"),_ => tail_error("invalid decimal: unknown character"),}} - replacement in common/decimal-parsers/src/lib.rs at line 402
if !HAS {tail_empty()} else {Ok(Decimal::from_parts(data as u32,(data >> 32) as u32,(data >> 64) as u32,NEG,scale as u32,))}#[inline(never)]#[cold]fn handle_full_128<const SAW_DECIMAL_SEPARATOR: bool, const NEG: bool>(mut data: u128,bytes: &[u8],scale: u8,next_byte: u8,) -> Result<Decimal, Error> {let b = next_byte;match b {b'0'..=b'9' => {let digit = u32::from(b - b'0');// If the data is going to overflow then we should go into recovery modelet next = (data * 10) + u128::from(digit);if overflow_128(next) {if SAW_DECIMAL_SEPARATOR {Err(Error::Underflow)} else {tail_error("invalid decimal: overflow from too many digits")}} else {data = next;let scale = scale + u8::from(SAW_DECIMAL_SEPARATOR);if let Some((next, bytes)) = bytes.split_first() {let next = *next;if SAW_DECIMAL_SEPARATOR && scale >= 28 {Err(Error::Underflow)} else {handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, next,)}} else {handle_data::<NEG, true>(data, scale)}}}DECIMAL_SEPARATOR if !SAW_DECIMAL_SEPARATOR => {// This call won't tail?if let Some((next, bytes)) = bytes.split_first() {handle_full_128::<true, NEG>(data, bytes, scale, *next)} else {handle_data::<NEG, true>(data, scale)}}GROUP_SEPARATOR => {if let Some((next, bytes)) = bytes.split_first() {handle_full_128::<SAW_DECIMAL_SEPARATOR, NEG>(data, bytes, scale, *next)} else {handle_data::<NEG, true>(data, scale)}}b => tail_invalid_digit(b),}}#[inline(never)]fn tail_empty() -> Result<Decimal, Error> {tail_error("invalid decimal: no digits found")}#[inline]fn handle_data<const NEG: bool, const HAS: bool>(data: u128,scale: u8,) -> Result<Decimal, Error> {debug_assert_eq!(data >> 96, 0);if !HAS {tail_empty()} else {Ok(Decimal::from_parts(data as u32,(data >> 32) as u32,(data >> 64) as u32,NEG,scale as u32,))}}}}; - edit in common/decimal-parsers/src/lib.rs at line 486[6.9592]
make_module!(english, b'.', b',');make_module!(german, b',', b'.'); - file move: Cargo.toml → Cargo.toml
- replacement in common/decimal-parsers/Cargo.toml at line 2
name = "german-decimal"name = "decimal-parsers" - edit in common/decimal-parsers/Cargo.toml at line 4
authors.workspace = true - edit in common/decimal-parsers/Cargo.toml at line 19[34.144]
[lints]workspace = true - edit in Cargo.toml at line 6
"common/german-decimal", - edit in Cargo.toml at line 93[6.7421][35.8316]
decimal-parsers.path = "common/decimal-parsers" - edit in Cargo.lock at line 199
"decimal-parsers", - edit in Cargo.lock at line 201
"german-decimal", - edit in Cargo.lock at line 221
"decimal-parsers", - edit in Cargo.lock at line 223
"german-decimal", - edit in Cargo.lock at line 260
"decimal-parsers", - edit in Cargo.lock at line 262
"german-decimal", - edit in Cargo.lock at line 280
"decimal-parsers", - edit in Cargo.lock at line 282
"german-decimal", - edit in Cargo.lock at line 323
"decimal-parsers", - edit in Cargo.lock at line 325
"german-decimal", - edit in Cargo.lock at line 340
"decimal-parsers", - edit in Cargo.lock at line 342
"german-decimal", - edit in Cargo.lock at line 359
"decimal-parsers", - edit in Cargo.lock at line 361
"german-decimal", - edit in Cargo.lock at line 375
"decimal-parsers", - edit in Cargo.lock at line 377
"german-decimal", - edit in Cargo.lock at line 1031
][[package]]name = "decimal-parsers"version = "0.0.0-dev.0"dependencies = ["proptest","rust_decimal","rust_decimal_macros","serde", - edit in Cargo.lock at line 1395
][[package]]name = "german-decimal"version = "0.0.0-dev.0"dependencies = ["proptest","rust_decimal","rust_decimal_macros","serde",