steam-user 0.1.0

Steam User web client for Rust - HTTP-based Steam Community interactions
Documentation
/// Parse a locale-aware Steam balance string into a float.
///
/// Steam renders balances in multiple locale formats:
/// - US/international: `.` as thousands separator, `.` as decimal → `"1,234.56"`
/// - European/Vietnamese: `.` as thousands separator, `,` as decimal → `"1.234,56"`
///
/// Detection heuristic: examine the **last** separator character in the
/// digit-and-separator-only string:
/// - Last separator is `,` → European/VN format (comma = decimal, dot = thousands)
/// - Last separator is `.` → US format (dot = decimal, comma = thousands)
/// - No separator at all → plain integer, parse directly
///
/// All non-digit, non-separator characters (currency symbols, spaces, `₫`,
/// `$`, etc.) are stripped before processing.
pub(crate) fn parse_steam_balance(s: &str) -> Option<f64> {
    // Keep only digits, dots, and commas
    let digits: String = s.chars().filter(|c| c.is_ascii_digit() || *c == '.' || *c == ',').collect();
    if digits.is_empty() {
        return None;
    }

    let has_dot = digits.contains('.');
    let has_comma = digits.contains(',');

    match (has_dot, has_comma) {
        (false, false) => digits.parse::<f64>().ok(),
        // Both present — last separator is the decimal, the other is thousands.
        (true, true) => {
            let last_sep = digits.chars().rev().find(|c| *c == '.' || *c == ',');
            if last_sep == Some(',') {
                digits.replace('.', "").replace(',', ".").parse::<f64>().ok()
            } else {
                digits.replace(',', "").parse::<f64>().ok()
            }
        }
        // Only `.` present. Ambiguous between US decimal and EU/VN thousands.
        // Heuristic: a single `.` followed by exactly 3 digits, or multiple
        // dots, is treated as a thousands grouping (Steam prices never have
        // 3 decimal places).
        (true, false) => {
            let dot_count = digits.matches('.').count();
            let after_last = digits.rsplit('.').next().unwrap_or("");
            if dot_count > 1 || after_last.len() == 3 {
                digits.replace('.', "").parse::<f64>().ok()
            } else {
                digits.parse::<f64>().ok()
            }
        }
        // Only `,` present. Symmetric heuristic.
        (false, true) => {
            let comma_count = digits.matches(',').count();
            let after_last = digits.rsplit(',').next().unwrap_or("");
            if comma_count > 1 || after_last.len() == 3 {
                digits.replace(',', "").parse::<f64>().ok()
            } else {
                digits.replace(',', ".").parse::<f64>().ok()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // --- Original VNĐ format tests ---

    #[test]
    fn test_with_decimal() {
        assert_eq!(parse_steam_balance("35.016,48₫"), Some(35016.48));
    }

    #[test]
    fn test_whole_number() {
        assert_eq!(parse_steam_balance("132.500₫"), Some(132500.0));
    }

    #[test]
    fn test_no_thousands() {
        assert_eq!(parse_steam_balance("500₫"), Some(500.0));
    }

    #[test]
    fn test_empty() {
        assert_eq!(parse_steam_balance(""), None);
        assert_eq!(parse_steam_balance("N/A"), None);
    }

    // --- Locale-aware format tests ---

    #[test]
    fn us_decimal_no_thousands() {
        // "12.34" → last sep is '.', US format → 12.34
        assert_eq!(parse_steam_balance("12.34"), Some(12.34));
    }

    #[test]
    fn us_decimal_with_thousands() {
        // "1,234.56" → last sep is '.', US format → 1234.56
        assert_eq!(parse_steam_balance("1,234.56"), Some(1234.56));
    }

    #[test]
    fn eu_decimal_no_thousands() {
        // "12,34" → last sep is ',', European format → 12.34
        assert_eq!(parse_steam_balance("12,34"), Some(12.34));
    }

    #[test]
    fn eu_decimal_with_thousands() {
        // "1.234,56" → last sep is ',', European format → 1234.56
        assert_eq!(parse_steam_balance("1.234,56"), Some(1234.56));
    }

    #[test]
    fn plain_integer() {
        // "1234" → no separator → 1234.0
        assert_eq!(parse_steam_balance("1234"), Some(1234.0));
    }
}