rustledger_core/
meta_json.rs1use crate::MetaValue;
19
20#[must_use]
27pub fn meta_value_to_json(value: &MetaValue) -> serde_json::Value {
28 match value {
29 MetaValue::String(s) => serde_json::Value::String(s.clone()),
30 MetaValue::Account(a) => serde_json::Value::String(a.to_string()),
31 MetaValue::Currency(c) => serde_json::Value::String(c.to_string()),
32 MetaValue::Tag(t) => serde_json::Value::String(t.to_string()),
33 MetaValue::Link(l) => serde_json::Value::String(l.to_string()),
34 MetaValue::Date(d) => serde_json::Value::String(d.to_string()),
35 MetaValue::Number(n) => serde_json::Value::String(n.to_string()),
36 MetaValue::Int(i) => serde_json::Value::String(i.to_string()),
37 MetaValue::Bool(b) => serde_json::Value::Bool(*b),
38 MetaValue::Amount(a) => serde_json::json!({
39 "number": a.number.to_string(),
40 "currency": a.currency.to_string(),
41 }),
42 MetaValue::None => serde_json::Value::Null,
43 }
44}
45
46#[must_use]
52pub const fn meta_value_type_tag(value: &MetaValue) -> &'static str {
53 match value {
54 MetaValue::String(_) => "string",
55 MetaValue::Account(_) => "account",
56 MetaValue::Currency(_) => "currency",
57 MetaValue::Tag(_) => "tag",
58 MetaValue::Link(_) => "link",
59 MetaValue::Date(_) => "date",
60 MetaValue::Number(_) => "number",
61 MetaValue::Int(_) => "int",
62 MetaValue::Bool(_) => "bool",
63 MetaValue::Amount(_) => "amount",
64 MetaValue::None => "null",
65 }
66}
67
68#[must_use]
77pub fn json_to_meta_value(value: &serde_json::Value) -> MetaValue {
78 use rust_decimal::Decimal;
79 match value {
80 serde_json::Value::String(s) => MetaValue::String(s.clone()),
81 serde_json::Value::Bool(b) => MetaValue::Bool(*b),
82 serde_json::Value::Number(n) => {
83 if let Some(i) = n.as_i64() {
84 MetaValue::Int(i)
85 } else {
86 Decimal::from_str_exact(&n.to_string()).map_or(MetaValue::None, MetaValue::Number)
90 }
91 }
92 serde_json::Value::Object(obj) => {
93 if let (Some(number), Some(currency)) = (obj.get("number"), obj.get("currency"))
94 && let (Some(n), Some(c)) = (number.as_str(), currency.as_str())
95 && let Ok(number) = Decimal::from_str_exact(n)
96 {
97 return MetaValue::Amount(crate::Amount {
98 number,
99 currency: c.into(),
100 });
101 }
102 MetaValue::None
103 }
104 serde_json::Value::Null | serde_json::Value::Array(_) => MetaValue::None,
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use rust_decimal_macros::dec;
112
113 #[test]
118 fn codec_covers_every_variant() {
119 let cases = [
120 MetaValue::String("hi".into()),
121 MetaValue::Account("Assets:Cash".into()),
122 MetaValue::Currency("USD".into()),
123 MetaValue::Tag("trip".into()),
124 MetaValue::Link("inv-1".into()),
125 MetaValue::Date(crate::naive_date(2024, 6, 15).unwrap()),
126 MetaValue::Number(dec!(123.456)),
127 MetaValue::Int(42),
128 MetaValue::Bool(true),
129 MetaValue::Amount(crate::Amount::new(dec!(99.99), "EUR")),
130 MetaValue::None,
131 ];
132 for mv in &cases {
133 assert!(!meta_value_type_tag(mv).is_empty());
135 let _ = meta_value_to_json(mv);
137 }
138 let tags: Vec<&str> = cases.iter().map(meta_value_type_tag).collect();
140 let mut uniq = tags.clone();
141 uniq.sort_unstable();
142 uniq.dedup();
143 assert_eq!(
144 uniq.len(),
145 tags.len(),
146 "type tags must be distinct: {tags:?}"
147 );
148 }
149
150 #[test]
151 fn json_round_trip() {
152 assert_eq!(
154 json_to_meta_value(&meta_value_to_json(&MetaValue::String("x".into()))),
155 MetaValue::String("x".into())
156 );
157 assert_eq!(
158 json_to_meta_value(&meta_value_to_json(&MetaValue::Bool(true))),
159 MetaValue::Bool(true)
160 );
161 assert_eq!(
162 json_to_meta_value(&meta_value_to_json(&MetaValue::None)),
163 MetaValue::None
164 );
165 assert_eq!(
166 json_to_meta_value(&meta_value_to_json(&MetaValue::Amount(crate::Amount::new(
167 dec!(1.50),
168 "USD"
169 )))),
170 MetaValue::Amount(crate::Amount::new(dec!(1.50), "USD"))
171 );
172 assert_eq!(
176 json_to_meta_value(&meta_value_to_json(&MetaValue::Int(7))),
177 MetaValue::String("7".into())
178 );
179 assert_eq!(json_to_meta_value(&serde_json::json!(7)), MetaValue::Int(7));
181 assert_eq!(
183 json_to_meta_value(&serde_json::json!(18_446_744_073_709_551_615_u64)),
184 MetaValue::Number(dec!(18446744073709551615))
185 );
186 }
187}