ccxt_core/
parser_utils.rs

1//! Common parser utilities for exchange data parsing.
2//!
3//! This module provides shared utility functions for parsing JSON data
4//! from exchange API responses into Rust types.
5
6use rust_decimal::Decimal;
7use rust_decimal::prelude::{FromPrimitive, FromStr};
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Parse a `Decimal` value from JSON (supports both string and number formats).
12///
13/// Empty strings are treated as `None`.
14pub fn parse_decimal(data: &Value, key: &str) -> Option<Decimal> {
15    data.get(key).and_then(|v| {
16        if let Some(num) = v.as_f64() {
17            Decimal::from_f64(num)
18        } else if let Some(s) = v.as_str() {
19            if s.is_empty() {
20                None
21            } else {
22                Decimal::from_str(s).ok()
23            }
24        } else {
25            None
26        }
27    })
28}
29
30/// Parse a timestamp from JSON (supports both string and number formats).
31pub fn parse_timestamp(data: &Value, key: &str) -> Option<i64> {
32    data.get(key).and_then(|v| {
33        v.as_i64()
34            .or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
35    })
36}
37
38/// Convert a JSON `Value` into a `HashMap<String, Value>`.
39pub fn value_to_hashmap(data: &Value) -> HashMap<String, Value> {
40    data.as_object()
41        .map(|obj| obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
42        .unwrap_or_default()
43}
44
45/// Convert millisecond timestamp to ISO8601 datetime string.
46pub fn timestamp_to_datetime(timestamp: i64) -> Option<String> {
47    chrono::DateTime::from_timestamp_millis(timestamp)
48        .map(|dt| dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string())
49}
50
51/// Convert ISO8601 datetime string to millisecond timestamp.
52pub fn datetime_to_timestamp(datetime: &str) -> Option<i64> {
53    chrono::DateTime::parse_from_rfc3339(datetime)
54        .ok()
55        .map(|dt| dt.timestamp_millis())
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use serde_json::json;
62
63    #[test]
64    fn test_parse_decimal_from_string() {
65        let data = json!({"price": "123.45"});
66        assert_eq!(
67            parse_decimal(&data, "price"),
68            Some(Decimal::from_str("123.45").unwrap())
69        );
70    }
71
72    #[test]
73    fn test_parse_decimal_from_number() {
74        let data = json!({"price": 123.45});
75        assert!(parse_decimal(&data, "price").is_some());
76    }
77
78    #[test]
79    fn test_parse_decimal_empty_string() {
80        let data = json!({"price": ""});
81        assert_eq!(parse_decimal(&data, "price"), None);
82    }
83
84    #[test]
85    fn test_parse_decimal_missing_key() {
86        let data = json!({"other": "123"});
87        assert_eq!(parse_decimal(&data, "price"), None);
88    }
89
90    #[test]
91    fn test_parse_timestamp_from_number() {
92        let data = json!({"time": 1704110400000i64});
93        assert_eq!(parse_timestamp(&data, "time"), Some(1704110400000));
94    }
95
96    #[test]
97    fn test_parse_timestamp_from_string() {
98        let data = json!({"time": "1704110400000"});
99        assert_eq!(parse_timestamp(&data, "time"), Some(1704110400000));
100    }
101
102    #[test]
103    fn test_value_to_hashmap() {
104        let data = json!({"a": 1, "b": "two"});
105        let map = value_to_hashmap(&data);
106        assert_eq!(map.len(), 2);
107        assert_eq!(map.get("a"), Some(&json!(1)));
108    }
109
110    #[test]
111    fn test_timestamp_to_datetime() {
112        let result = timestamp_to_datetime(1704110400000);
113        assert!(result.is_some());
114        assert!(result.unwrap().contains("2024-01-01"));
115    }
116
117    #[test]
118    fn test_datetime_to_timestamp() {
119        let result = datetime_to_timestamp("2024-01-01T12:00:00.000Z");
120        assert!(result.is_some());
121    }
122}