serde.rs (4855B)
1 #![cfg(feature = "serde")] 2 3 mod common; 4 5 use core::str::FromStr; 6 use std::collections::BTreeSet; 7 8 use radroots_core::{Currency, Decimal, Money, Percent, Quantity, QuantityPrice, Unit}; 9 use serde_json::Value; 10 11 const SERIALIZATION_VECTORS: &str = include_str!("fixtures/value_serialization.v1.json"); 12 13 #[test] 14 fn decimal_serializes_as_string() { 15 let d = common::dec("1.2300"); 16 let json = serde_json::to_string(&d).unwrap(); 17 assert_eq!(json, "\"1.23\""); 18 19 let back: Decimal = serde_json::from_str(&json).unwrap(); 20 assert_eq!(back, common::dec("1.23")); 21 } 22 23 #[test] 24 fn quantity_uses_decimal_str_and_omits_empty_label() { 25 let q = Quantity::try_new(common::dec("1.2300"), Unit::MassKg).unwrap(); 26 let value = serde_json::to_value(&q).unwrap(); 27 28 assert_eq!(value["amount"], Value::String("1.23".to_string())); 29 assert_eq!(value["unit"], Value::String("kg".to_string())); 30 assert!(value.get("label").is_none()); 31 } 32 33 #[test] 34 fn quantity_deserializes_decimal_str_via_serde_ext() { 35 let raw = r#"{"amount":"1.2300","unit":"kg","label":"bag"}"#; 36 let q: Quantity = serde_json::from_str(raw).unwrap(); 37 assert_eq!(q.amount(), common::dec("1.23")); 38 assert_eq!(q.unit(), Unit::MassKg); 39 assert_eq!(q.label(), Some("bag")); 40 } 41 42 #[test] 43 fn quantity_rejects_non_string_decimal_amount() { 44 let raw = r#"{"amount":1.23,"unit":"kg"}"#; 45 let err = serde_json::from_str::<Quantity>(raw).unwrap_err(); 46 assert!(err.to_string().contains("invalid type")); 47 } 48 49 #[test] 50 fn money_and_percent_roundtrip_with_strings() { 51 let money = Money::try_new(common::dec("2.50"), Currency::USD).unwrap(); 52 let value = serde_json::to_value(&money).unwrap(); 53 assert_eq!(value["amount"], Value::String("2.5".to_string())); 54 assert_eq!(value["currency"], Value::String("USD".to_string())); 55 56 let pct = Percent::new(common::dec("12.5")); 57 let value = serde_json::to_value(&pct).unwrap(); 58 assert_eq!(value["value"], Value::String("12.5".to_string())); 59 } 60 61 #[test] 62 fn native_value_deserialization_enforces_invariants() { 63 let negative_money = r#"{"amount":"-0.01","currency":"USD"}"#; 64 assert!( 65 serde_json::from_str::<Money>(negative_money) 66 .unwrap_err() 67 .to_string() 68 .contains("money amount") 69 ); 70 71 let negative_quantity = r#"{"amount":"-1","unit":"each"}"#; 72 assert!( 73 serde_json::from_str::<Quantity>(negative_quantity) 74 .unwrap_err() 75 .to_string() 76 .contains("quantity amount") 77 ); 78 79 let zero_price = 80 r#"{"amount":{"amount":"1","currency":"USD"},"quantity":{"amount":"0","unit":"each"}}"#; 81 assert!( 82 serde_json::from_str::<QuantityPrice>(zero_price) 83 .unwrap_err() 84 .to_string() 85 .contains("greater than zero") 86 ); 87 } 88 89 #[test] 90 fn currency_serializes_as_code() { 91 let c = Currency::from_str("usd").unwrap(); 92 let json = serde_json::to_string(&c).unwrap(); 93 assert_eq!(json, "\"USD\""); 94 } 95 96 #[test] 97 fn canonical_serialization_vectors_roundtrip_through_public_types() { 98 let suite: Value = serde_json::from_str(SERIALIZATION_VECTORS).unwrap(); 99 assert_eq!(suite["suite"], "core_value_serialization"); 100 assert_eq!(suite["contract_version"], "1.0.0"); 101 102 let vectors = suite["vectors"].as_array().unwrap(); 103 let mut ids = BTreeSet::new(); 104 for vector in vectors { 105 let id = vector["id"].as_str().unwrap(); 106 assert!(ids.insert(id), "duplicate vector id {id}"); 107 let input = vector["input"].clone(); 108 let actual = match vector["kind"].as_str().unwrap() { 109 "core.decimal.serde" => { 110 serde_json::to_value(serde_json::from_value::<Decimal>(input).unwrap()).unwrap() 111 } 112 "core.currency.serde" => { 113 serde_json::to_value(serde_json::from_value::<Currency>(input).unwrap()).unwrap() 114 } 115 "core.money.serde" => { 116 serde_json::to_value(serde_json::from_value::<Money>(input).unwrap()).unwrap() 117 } 118 "core.percent.serde" => { 119 serde_json::to_value(serde_json::from_value::<Percent>(input).unwrap()).unwrap() 120 } 121 "core.quantity.serde" => { 122 serde_json::to_value(serde_json::from_value::<Quantity>(input).unwrap()).unwrap() 123 } 124 "core.unit.serde" => { 125 serde_json::to_value(serde_json::from_value::<Unit>(input).unwrap()).unwrap() 126 } 127 "core.quantity_price.serde" => { 128 serde_json::to_value(serde_json::from_value::<QuantityPrice>(input).unwrap()) 129 .unwrap() 130 } 131 kind => panic!("unsupported serialization vector kind {kind}"), 132 }; 133 assert_eq!(actual, vector["expected"], "vector {id}"); 134 } 135 assert_eq!(ids.len(), 9); 136 }