Skip to main content

limitless/serde_helpers/
mod.rs

1use crate::prelude::*;
2
3/// Deserialize a string as `Option<f64>`, treating empty strings as `None`.
4///
5/// The Limitless API sometimes returns empty strings for optional numeric
6/// fields. This module handles that gracefully.
7pub mod string_to_float_optional {
8    use super::*;
9
10    pub fn serialize<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
11    where
12        S: Serializer,
13    {
14        match value {
15            Some(v) => serializer.serialize_str(&v.to_string()),
16            None => serializer.serialize_str(""),
17        }
18    }
19
20    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
21    where
22        D: Deserializer<'de>,
23    {
24        let s: Option<String> = Option::deserialize(deserializer)?;
25        match s {
26            Some(s) if s.trim().is_empty() => Ok(None),
27            Some(s) => f64::from_str(&s)
28                .map(Some)
29                .map_err(serde::de::Error::custom),
30            None => Ok(None),
31        }
32    }
33}
34
35/// Deserialize a string as `f64`.
36///
37/// The Limitless API returns many numeric values (prices, amounts) as strings
38/// to preserve precision. This converts them to `f64`.
39pub mod string_to_float {
40    use super::*;
41
42    pub fn serialize<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
43    where
44        S: Serializer,
45    {
46        serializer.serialize_str(&value.to_string())
47    }
48
49    pub fn deserialize<'de, D>(deserializer: D) -> Result<f64, D::Error>
50    where
51        D: Deserializer<'de>,
52    {
53        let s = String::deserialize(deserializer)?;
54        s.parse::<f64>().map_err(serde::de::Error::custom)
55    }
56}
57
58/// Deserialize a string as `u64`.
59pub mod string_to_u64 {
60    use super::*;
61
62    pub fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
63    where
64        S: Serializer,
65    {
66        serializer.serialize_str(&value.to_string())
67    }
68
69    pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
70    where
71        D: Deserializer<'de>,
72    {
73        let s = String::deserialize(deserializer)?;
74        s.parse::<u64>().map_err(serde::de::Error::custom)
75    }
76}
77
78/// Deserialize a string as `Option<u64>`, treating empty strings as `None`.
79pub mod string_to_u64_optional {
80    use super::*;
81
82    pub fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
83    where
84        S: Serializer,
85    {
86        match value {
87            Some(v) => serializer.serialize_str(&v.to_string()),
88            None => serializer.serialize_str(""),
89        }
90    }
91
92    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
93    where
94        D: Deserializer<'de>,
95    {
96        let s: Option<String> = Option::deserialize(deserializer)?;
97        match s {
98            Some(s) if s.trim().is_empty() => Ok(None),
99            Some(s) => u64::from_str(&s)
100                .map(Some)
101                .map_err(serde::de::Error::custom),
102            None => Ok(None),
103        }
104    }
105}
106
107/// Treat empty strings as `None` for any `Deserialize` type.
108pub fn empty_string_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
109where
110    D: Deserializer<'de>,
111    T: Deserialize<'de>,
112{
113    let s: Option<String> = Option::deserialize(deserializer)?;
114    match s {
115        Some(ref s) if s.trim().is_empty() => Ok(None),
116        Some(s) => {
117            let kind = T::deserialize(serde_json::Value::String(s))
118                .map(Some)
119                .map_err(serde::de::Error::custom)?;
120            Ok(kind)
121        }
122        None => Ok(None),
123    }
124}