1use serde::de::{Error, Visitor};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt::{Display, Formatter};
4use std::str::FromStr;
5
6#[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 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}