use crate::{Currency, MoneyError};
fn parse_into_string_amount<'a>(
integer_part: &'a str,
decimal_part: Option<&'a str>,
thousand_separator: &'a str,
is_negative: bool,
) -> Result<String, MoneyError> {
if integer_part.is_empty() {
return Err(MoneyError::ParseStrError("integer part is empty".into()));
}
if integer_part.contains(thousand_separator) {
let groups: Vec<&str> = integer_part.split(thousand_separator).collect();
if groups[0].is_empty()
|| groups[0].len() > 3
|| !groups[0].chars().all(|c| c.is_ascii_digit())
{
return Err(MoneyError::ParseStrError(format!("first group of integer part is empty or more than 3 digits or not all ascii numbers: {}", integer_part).into()));
}
for group in groups.iter().skip(1) {
if group.len() != 3 || !group.chars().all(|c| c.is_ascii_digit()) {
return Err(MoneyError::ParseStrError(format!("second and subsequent parts of integer is not 3 digits or not all ascii numbers: {}", integer_part).into()));
}
}
let mut result = groups.join("");
if let Some(dec) = decimal_part {
if dec.is_empty() || !dec.chars().all(|c| c.is_ascii_digit()) {
return Err(MoneyError::ParseStrError(
"decimal part is empty or not all ascii numbers".into(),
));
}
result.push('.');
result.push_str(dec);
}
if is_negative {
result.insert(0, '-');
}
Ok(result)
} else {
if !integer_part.chars().all(|c| c.is_ascii_digit()) {
return Err(MoneyError::ParseStrError(
"integer part not all ascii numbers".into(),
));
}
let mut result = integer_part.to_string();
if let Some(dec) = decimal_part {
if dec.is_empty() || !dec.chars().all(|c| c.is_ascii_digit()) {
return Err(MoneyError::ParseStrError(
"decimal part is empty or not all ascii numbers".into(),
));
}
result.push('.');
result.push_str(dec);
}
if is_negative {
result.insert(0, '-');
}
Ok(result)
}
}
pub(crate) fn parse_str_code<C: Currency>(
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<String, MoneyError> {
parse_str_code_internal(C::CODE, money_str, thousand_separator, decimal_separator)
}
#[inline]
pub(crate) fn parse_str_code_internal(
code: &str,
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<String, MoneyError> {
let str_code = money_str.trim();
let parts: Vec<&str> = str_code.split_whitespace().collect();
if parts.len() != 2
|| parts[0].is_empty()
|| !parts[0].chars().all(|c| c.is_ascii_alphabetic())
|| parts[1].is_empty()
{
return Err(MoneyError::ParseStrError(
format!(
"invalid currency with code, expected: <CODE> <AMOUNT> with <CODE> and <AMOUNT> all in ascii, found: {}",
str_code
)
.into(),
));
}
let currency_code = parts[0];
let amount_str = parts[1];
if currency_code != code {
return Err(MoneyError::CurrencyMismatchError(
currency_code.into(),
code.into(),
));
}
let amount_parts: Vec<&str> = amount_str.split(decimal_separator).collect();
if amount_parts.len() > 2 {
return Err(MoneyError::ParseStrError(
format!(
"splitting by decimal separator({}) must not more than 2 parts: {}",
decimal_separator, amount_str
)
.into(),
));
}
let (integer_part, is_negative) = if let Some(neg_trimmed) = amount_parts[0].strip_prefix("-") {
(neg_trimmed, true)
} else {
(amount_parts[0], false)
};
let decimal_part = if amount_parts.len() == 2 {
Some(amount_parts[1])
} else {
None
};
parse_into_string_amount(integer_part, decimal_part, thousand_separator, is_negative)
}
pub(crate) fn parse_str_symbol<C: Currency>(
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<String, MoneyError> {
parse_str_symbol_internal(C::SYMBOL, money_str, thousand_separator, decimal_separator)
}
#[inline]
pub(crate) fn parse_str_symbol_internal(
symbol: &str,
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<String, MoneyError> {
let str_symbol = money_str.trim();
let (abs_money, is_negative) = if let Some(trimmed) = str_symbol.strip_prefix('-') {
(trimmed, true)
} else {
(str_symbol, false)
};
let amount_str = abs_money.strip_prefix(symbol);
let amount_str = if let Some(amount) = amount_str
&& !amount.is_empty()
{
amount
} else {
return Err(MoneyError::CurrencyMismatchError(
str_symbol.into(),
symbol.into(),
));
};
let amount_parts: Vec<&str> = amount_str.split(decimal_separator).collect();
if amount_parts.len() > 2 {
return Err(MoneyError::ParseStrError(
format!(
"splitting by decimal separator({}) must not more than 2 parts: {}",
decimal_separator, amount_str
)
.into(),
));
}
let (integer_part, decimal_part) = if amount_parts.len() == 2 {
(amount_parts[0], Some(amount_parts[1]))
} else {
(amount_parts[0], None)
};
parse_into_string_amount(integer_part, decimal_part, thousand_separator, is_negative)
}