itf 0.4.0

Library for consuming Apalache ITF traces
Documentation
use serde::Deserialize;

#[test]
fn test_tuple() {
    let itf = serde_json::json!({"#tup": [1, 2, 3]});

    let _: [u8; 3] = itf::from_value(itf.clone()).unwrap();
    let _: (u8, u8, u8) = itf::from_value(itf.clone()).unwrap();
    let _: Vec<u8> = itf::from_value(itf.clone()).unwrap();
    let _: std::collections::HashSet<u8> = itf::from_value(itf.clone()).unwrap();
    let _: std::collections::BTreeSet<u8> = itf::from_value(itf.clone()).unwrap();
}

#[test]
fn test_set() {
    let itf = serde_json::json!({"#set": [1, 2, 3]});

    let _: [u8; 3] = itf::from_value(itf.clone()).unwrap();
    let _: (u8, u8, u8) = itf::from_value(itf.clone()).unwrap();
    let _: Vec<u8> = itf::from_value(itf.clone()).unwrap();
    let _: std::collections::HashSet<u8> = itf::from_value(itf.clone()).unwrap();
    let _: std::collections::BTreeSet<u8> = itf::from_value(itf.clone()).unwrap();
    let _: serde_json::Value = itf::from_value(itf.clone()).unwrap();
}

#[test]
fn test_bigint_deser() {
    let itf = serde_json::json!({"#bigint": "-99"});

    // successful case; only BigInt
    assert_eq!(
        dashu_int::IBig::from(-99),
        itf::from_value(itf.clone()).unwrap()
    );

    assert_eq!(itf::from_value::<i64>(itf.clone()).unwrap(), -99);

    // unsuccessful cases
    assert!(itf::from_value::<u64>(itf.clone()).is_err());
    assert!(itf::from_value::<itf::value::BigInt>(itf.clone()).is_err());
    assert!(!matches!(
        itf::from_value::<itf::Value>(itf.clone()).unwrap(),
        itf::Value::BigInt(_),
    ));
}

#[test]
fn test_biguint_deser() {
    let itf = serde_json::json!({"#bigint": "99"});

    assert_eq!(
        dashu_int::IBig::from(99u32),
        itf::from_value(itf.clone()).unwrap()
    );

    assert_eq!(
        dashu_int::UBig::from(99u32),
        itf::from_value(itf.clone()).unwrap()
    );

    assert_eq!(99, itf::from_value::<i64>(itf.clone()).unwrap());
    assert_eq!(99, itf::from_value::<u64>(itf.clone()).unwrap());

    // unsuccessful cases
    assert!(itf::from_value::<itf::value::BigInt>(itf.clone()).is_err());
    assert!(!matches!(
        itf::from_value::<itf::Value>(itf.clone()).unwrap(),
        itf::Value::BigInt(_),
    ));
}

#[test]
fn test_itf_value_equivalent() {
    let itf = serde_json::json!({
        "bool": true,
        "number": -99,
        "str": "hello",
        "list": [1, 2, 3],
        "record": {"a": 1, "b": 2, "c": 3},
    });

    let value = serde_json::from_value::<itf::Value>(itf.clone()).unwrap();
    assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap());
}

#[test]
#[should_panic]
fn test_itf_value_noneq() {
    // Deserialized Value loses the type information
    let itf = serde_json::json!({
        "bigint": {"#bigint": "-999"},
        "tuple": {"#tup": [1, 2, 3]},
        "set": {"#set": [1, 2, 3]},
        "map": {"#map": [["1", 3], ["2", 4]]},
    });

    let value = serde_json::from_value::<itf::Value>(itf.clone()).unwrap();
    assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap());
}

#[test]
#[should_panic]
fn test_map_with_non_str_key() {
    // MapSerializer accepts only string keys
    let itf = serde_json::json!({
        "map": {"#map": [[1, 3], [2, 4]]},
    });

    let value = serde_json::from_value::<itf::Value>(itf).unwrap();
    itf::Value::deserialize(value).unwrap();
}

#[test]
fn test_bigint_to_int() {
    let itf = serde_json::json!({
        // i64::MIN - 1
        "#bigint": "-9223372036854775809",
    });

    assert!(itf::from_value::<i64>(itf.clone()).is_err());
    assert!(itf::from_value::<dashu_int::IBig>(itf).is_ok());
}

#[test]
fn test_deserialize_any() {
    use dashu_int::IBig;
    use itf::de::{As, Integer, Same};
    use std::collections::HashMap;

    let itf = serde_json::json!([{
        "_foo": {"#map": [[{"#bigint": "1"}, {"#bigint": "2"}]]},
        "typ": "Foo",
    },
    {
        "_bar": [[[{"#bigint": "1"}, {"#bigint": "2"}]]],
        "typ": "Bar",
    }

    ]);

    // deserialize as bigints
    #[derive(Deserialize, Debug)]
    #[serde(tag = "typ")]
    enum FooBarBigInt {
        Foo { _foo: HashMap<IBig, IBig> },
        Bar { _bar: Vec<Vec<(IBig, IBig)>> },
    }
    itf::from_value::<Vec<FooBarBigInt>>(itf.clone()).unwrap();

    // deserialize as i64
    #[derive(Deserialize, Debug)]
    #[serde(tag = "typ")]
    enum FooBarInt {
        // try to deserialize _foo as i64, instead of IBig
        Foo {
            #[serde(with = "As::<HashMap<Integer, Integer>>")]
            _foo: HashMap<i64, i64>,
        },
        Bar {
            #[serde(with = "As::<Vec<Vec<(Integer, Integer)>>>")]
            _bar: Vec<Vec<(i64, i64)>>,
        },
    }
    itf::from_value::<Vec<FooBarInt>>(itf.clone()).unwrap();

    // deserialize as mix
    #[derive(Deserialize, Debug)]
    #[serde(tag = "typ")]
    enum FooBarMixInt {
        // try to deserialize _foo as i64, instead of IBig
        Foo {
            #[serde(with = "As::<HashMap<Integer, Integer>>")]
            _foo: HashMap<i64, IBig>,
        },
        Bar {
            #[serde(with = "As::<Vec<Vec<(Same, Integer)>>>")]
            _bar: Vec<Vec<(IBig, u64)>>,
        },
    }
    itf::from_value::<Vec<FooBarMixInt>>(itf.clone()).unwrap();
}

#[test]
fn test_failed_bare_bigint_to_int() {
    use itf::de::Integer;
    use serde_with::de::DeserializeAsWrap;

    let itf = serde_json::json!({
        "#bigint": "12",
    });

    let itf_value = serde_json::from_value::<itf::Value>(itf.clone()).unwrap();

    assert!(i64::deserialize(itf_value.clone()).is_ok());
    assert!(DeserializeAsWrap::<i64, Integer>::deserialize(itf_value).is_ok());
}

#[test]
fn test_complete() {
    use dashu_int::IBig;
    use std::collections::{BTreeSet, HashMap, HashSet};

    #[allow(dead_code)]
    #[derive(Deserialize, Debug)]
    #[serde(untagged)]
    enum RecordEnum {
        One(i64, String),
        Two { _foo: String, _bar: i64 },
    }

    #[allow(dead_code)]
    #[derive(Deserialize, Debug)]
    struct Rec {
        foo: IBig,
        bar: IBig,
    }

    #[derive(Deserialize, Debug)]
    struct Complete {
        _bool: bool,
        _number: i64,
        _str: String,
        _bigint: IBig,
        _list: Vec<IBig>,
        _tuple: (String, IBig),
        _set: HashSet<IBig>,
        _map: HashMap<BTreeSet<IBig>, IBig>,
        _record: Rec,
        _enum: Vec<RecordEnum>,
    }

    let itf = serde_json::json!({
        "_bool": true,
        "_number": -99,
        "_str": "hello",
        "_bigint": {"#bigint": "-999"},
        "_int_from_bigint": {"#bigint": "-999"},
        "_bigint_from_int": -999,
        "_list": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}],
        "_tuple": {"#tup": ["hello", {"#bigint": "999"}]},
        "_set": {"#set": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}]},
        "_map": {"#map": [[{"#set": [{"#bigint": "1"}, {"#bigint": "2"}]}, {"#bigint": "3"}], [{"#set": [{"#bigint": "2"}, {"#bigint": "3"}]}, {"#bigint": "4"}]]},
        "_record": {"foo": {"#bigint": "1"}, "bar": {"#bigint": "2"}},
        "_enum": [{"#tup": [1, "hello"]}, {"_foo": "hello", "_bar": 1}],
    });

    let _: Complete = itf::from_value(itf).unwrap();
}

// Test extracted from the malachite MBT tests
#[test]
fn test_enum_unit_variant() {
    #[derive(Debug, PartialEq, Deserialize)]
    #[serde(tag = "tag", content = "value")]
    pub enum VKOutput {
        #[serde(rename = "NoVKOutput")]
        NoOutput,
    }

    let obj = serde_json::json!({
        "lastEmitted": {
            "tag": "NoVKOutput",
            "value": {
                "#tup": []
            }
        }
    });

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct Test {
        last_emitted: VKOutput,
    }

    let itf_value = itf::from_value::<Test>(obj).unwrap();
    assert_eq!(itf_value.last_emitted, VKOutput::NoOutput);
}