Skip to main content

greed/
asset.rs

1use serde::de::{Error, Visitor};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt::{Display, Formatter};
4use std::str::FromStr;
5
6/// A symbol representing an asset (for example a stock such as VTI).
7#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
8pub struct AssetSymbol {
9    pub symbol: String,
10}
11
12impl AssetSymbol {
13    pub fn new(symbol: &str) -> Self {
14        Self {
15            symbol: symbol.to_string(),
16        }
17    }
18}
19
20impl FromStr for AssetSymbol {
21    type Err = ();
22
23    fn from_str(s: &str) -> Result<Self, Self::Err> {
24        let symbol = s.trim_start_matches("$").to_string();
25        Ok(AssetSymbol { symbol })
26    }
27}
28
29impl From<String> for AssetSymbol {
30    fn from(value: String) -> Self {
31        AssetSymbol::from_str(&value).expect("asset symbol from_str shouldn't fail")
32    }
33}
34
35impl From<&str> for AssetSymbol {
36    fn from(value: &str) -> Self {
37        AssetSymbol::from_str(value).expect("asset symbol from_str shouldn't fail")
38    }
39}
40
41struct AssetVisitor;
42
43impl<'de> Visitor<'de> for AssetVisitor {
44    type Value = AssetSymbol;
45
46    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
47        formatter.write_str("expecting a string with or without a $ prefix")
48    }
49
50    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
51    where
52        E: Error,
53    {
54        // This parser never returns an error so we can just unwrap.
55        let asset = v.parse::<AssetSymbol>().unwrap();
56        Ok(asset)
57    }
58}
59
60impl<'de> Deserialize<'de> for AssetSymbol {
61    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62    where
63        D: Deserializer<'de>,
64    {
65        deserializer.deserialize_str(AssetVisitor)
66    }
67}
68
69impl Serialize for AssetSymbol {
70    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: Serializer,
73    {
74        serializer.serialize_str(&self.symbol)
75    }
76}
77
78impl Display for AssetSymbol {
79    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", self.symbol.to_uppercase())
81    }
82}
83
84#[cfg(test)]
85mod test {
86    use crate::asset::AssetSymbol;
87    use serde::{Deserialize, Serialize};
88    use serde_json::json;
89
90    #[test]
91    fn display() {
92        let asset = "vti".parse::<AssetSymbol>().expect("failed to parse asset");
93        let display = asset.to_string();
94        assert_eq!(display, "VTI")
95    }
96
97    #[test]
98    fn from_string_no_dollar_sign() {
99        let asset = "vti".parse::<AssetSymbol>().expect("failed to parse asset");
100        let expected = AssetSymbol::new("vti");
101        assert_eq!(asset, expected);
102    }
103
104    #[test]
105    fn from_string_with_dollar_sign() {
106        let asset = "$vti"
107            .parse::<AssetSymbol>()
108            .expect("failed to parse asset");
109        let expected = AssetSymbol::new("vti");
110        assert_eq!(asset, expected);
111    }
112
113    #[test]
114    fn from_str_with_dollar_sign() {
115        let asset: AssetSymbol = "$vti".into();
116        let expected = AssetSymbol::new("vti");
117        assert_eq!(asset, expected);
118    }
119
120    #[derive(Debug, Deserialize, PartialEq, Serialize)]
121    struct TestAssetContainer {
122        asset: AssetSymbol,
123    }
124
125    #[test]
126    fn deserialize() {
127        let json = json!({
128            "asset": "$vti"
129        });
130        let container =
131            serde_json::from_value::<TestAssetContainer>(json).expect("failed to deserialize");
132        let expected = TestAssetContainer {
133            asset: AssetSymbol::new("vti"),
134        };
135        assert_eq!(expected, container)
136    }
137
138    #[test]
139    fn serialize() {
140        let test_container = TestAssetContainer {
141            asset: AssetSymbol {
142                symbol: "vti".to_string(),
143            },
144        };
145        let expected_json = json!({
146            "asset": "vti"
147        });
148        let expected = serde_json::to_string_pretty(&expected_json).unwrap();
149        let json = serde_json::to_string_pretty(&test_container).expect("failed to serialize");
150
151        assert_eq!(json, expected)
152    }
153}