lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }