dasl 0.2.0

Data-Adressed Structures & Links
Documentation
use std::{
    convert::{TryFrom, TryInto},
    str::FromStr,
};

use dasl::{
    cid::Cid,
    drisl::{Value, from_slice, to_vec},
};
use serde::{Deserialize, Serialize, de};
use serde_bytes::ByteBuf;

#[test]
fn test_cid_struct() {
    #[derive(Debug, PartialEq, Deserialize, Serialize)]
    struct MyStruct {
        cid: Cid,
        data: bool,
    }

    let cid = Cid::from_str("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy").unwrap();
    let cid_encoded = to_vec(&cid).unwrap();
    assert_eq!(
        cid_encoded,
        [
            0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68,
            0xff, 0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d,
            0x70, 0x64, 0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
        ]
    );

    let cid_decoded_as_cid: Cid = from_slice(&cid_encoded).unwrap();
    assert_eq!(cid_decoded_as_cid, cid);

    let cid_decoded_as_drisl: Value = from_slice(&cid_encoded).unwrap();
    assert_eq!(cid_decoded_as_drisl, Value::Cid(cid));

    // Tests with the Type nested in a struct

    let mystruct = MyStruct { cid, data: true };
    let mystruct_encoded = to_vec(&mystruct).unwrap();
    assert_eq!(
        mystruct_encoded,
        [
            0xa2, 0x63, 0x63, 0x69, 0x64, 0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20,
            0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff, 0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30,
            0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64, 0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88,
            0x62, 0x66, 0xe7, 0xae, 0x64, 0x64, 0x61, 0x74, 0x61, 0xf5
        ]
    );

    let mystruct_decoded_as_mystruct: MyStruct = from_slice(&mystruct_encoded).unwrap();
    assert_eq!(mystruct_decoded_as_mystruct, mystruct);

    let mystruct_decoded_as_drisl: Value = from_slice(&mystruct_encoded).unwrap();
    let mut expected_map = std::collections::BTreeMap::new();
    expected_map.insert("cid".to_string(), Value::Cid(cid));
    expected_map.insert("data".to_string(), Value::Bool(true));
    assert_eq!(mystruct_decoded_as_drisl, Value::Map(expected_map));
}

/// Test that arbitrary bytes are not interpreted as CID.
#[test]
fn test_binary_not_as_cid() {
    // h'affe'
    // 42      # bytes(2)
    //    AFFE # "\xAF\xFE"
    let bytes = [0x42, 0xaf, 0xfe];
    let bytes_as_drisl: Value = from_slice(&bytes).unwrap();
    assert_eq!(bytes_as_drisl, Value::Bytes(vec![0xaf, 0xfe]));
}

/// Test that CIDs don't decode into byte buffers, lists, etc.
#[test]
fn test_cid_not_as_bytes() {
    let cbor_cid = [
        0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff,
        0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64,
        0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
    ];
    from_slice::<Vec<u8>>(&cbor_cid).expect_err("shouldn't have parsed a tagged CID as a sequence");
    from_slice::<serde_bytes::ByteBuf>(&cbor_cid)
        .expect_err("shouldn't have parsed a tagged CID as a byte array");
    from_slice::<serde_bytes::ByteBuf>(&cbor_cid[2..])
        .expect("should have parsed an untagged CID as a byte array");

    #[derive(Debug, Deserialize, PartialEq)]
    struct NewType(ByteBuf);

    #[derive(Debug, Deserialize, PartialEq)]
    #[serde(untagged)]
    enum BytesInEnum {
        MyCid(NewType),
    }

    // decode the CID (without the TAG and the zero prefix) as bytes.
    let cid_without_tag = &cbor_cid[5..];
    assert_eq!(
        from_slice::<BytesInEnum>(&cbor_cid).unwrap(),
        BytesInEnum::MyCid(NewType(ByteBuf::from(cid_without_tag)))
    );
}

/// Test whether a binary CID could be serialized if it isn't prefixed by tag 42. It should fail.
#[test]
fn test_cid_bytes_without_tag() {
    let cbor_cid = [
        0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff,
        0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64,
        0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
    ];
    let decoded_cbor_cid: Cid = from_slice(&cbor_cid).unwrap();
    assert_eq!(decoded_cbor_cid.as_bytes(), &cbor_cid[5..]);

    // The CID without the tag 42 prefix
    let cbor_bytes = &cbor_cid[2..];
    from_slice::<Cid>(cbor_bytes).expect_err("should have failed to decode bytes as cid");
}

/// This test shows how a kinded enum could be implemented.
#[test]
fn test_cid_in_kinded_enum() {
    #[derive(Debug, PartialEq)]
    pub enum Kinded {
        Bytes(ByteBuf),
        Link(Cid),
    }

    let cbor_cid = [
        0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff,
        0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64,
        0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
    ];

    impl TryFrom<Value> for Kinded {
        type Error = ();

        fn try_from(drisl: Value) -> Result<Self, Self::Error> {
            match drisl {
                Value::Bytes(bytes) => Ok(Self::Bytes(ByteBuf::from(bytes))),
                Value::Cid(cid) => Ok(Self::Link(cid)),
                _ => Err(()),
            }
        }
    }

    impl<'de> de::Deserialize<'de> for Kinded {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: de::Deserializer<'de>,
        {
            Value::deserialize(deserializer).and_then(|drisl| {
                drisl
                    .try_into()
                    .map_err(|_| de::Error::custom("No matching enum variant found"))
            })
        }
    }

    let decoded_cid: Kinded = from_slice(&cbor_cid).unwrap();
    let cid = Cid::from_bytes_raw(&cbor_cid[5..]).unwrap();
    assert_eq!(decoded_cid, Kinded::Link(cid));

    // The CID without the tag 42 prefix
    let cbor_bytes = &cbor_cid[2..];
    let decoded_bytes: Kinded = from_slice(cbor_bytes).unwrap();
    // The CBOR decoded bytes don't contain the prefix with the bytes type identifier and the
    // length.
    let bytes = cbor_bytes[2..].to_vec();
    assert_eq!(decoded_bytes, Kinded::Bytes(ByteBuf::from(bytes)));

    // Check that random bytes cannot be deserialized.
    let random_bytes = &cbor_cid[10..];
    let decoded_random_bytes: Result<Kinded, _> = from_slice(random_bytes);
    assert!(decoded_random_bytes.is_err());
}

/// This test shows how a kinded enum could be implemented, when bytes as well as a CID are wrapped
/// in a newtype struct.
#[test]
fn test_cid_in_kinded_enum_with_newtype() {
    #[derive(Debug, Deserialize, PartialEq)]
    pub struct Foo(#[serde(with = "serde_bytes")] Vec<u8>);

    #[derive(Debug, PartialEq)]
    pub enum Kinded {
        MyBytes(Foo),
        Link(Cid),
    }

    let cbor_cid = [
        0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff,
        0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64,
        0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
    ];

    impl TryFrom<Value> for Kinded {
        type Error = ();

        fn try_from(drisl: Value) -> Result<Self, Self::Error> {
            match drisl {
                Value::Bytes(bytes) => Ok(Self::MyBytes(Foo(bytes))),
                Value::Cid(cid) => Ok(Self::Link(cid)),
                _ => Err(()),
            }
        }
    }

    impl<'de> de::Deserialize<'de> for Kinded {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: de::Deserializer<'de>,
        {
            Value::deserialize(deserializer).and_then(|drisl| {
                drisl
                    .try_into()
                    .map_err(|_| de::Error::custom("No matching enum variant found"))
            })
        }
    }

    let decoded_cid: Kinded = from_slice(&cbor_cid).unwrap();
    // The actual CID is without the CBOR tag 42, the byte identifier and the data length.
    let cid = Cid::from_bytes_raw(&cbor_cid[5..]).unwrap();
    assert_eq!(decoded_cid, Kinded::Link(cid));

    // The CID without the tag 42 prefix
    let cbor_bytes = &cbor_cid[2..];
    let decoded_bytes: Kinded = from_slice(cbor_bytes).unwrap();
    // The CBOR decoded bytes don't contain the prefix with the bytes type identifier and the
    // length.
    let bytes = cbor_bytes[2..].to_vec();
    assert_eq!(decoded_bytes, Kinded::MyBytes(Foo(bytes)));

    // Check that random bytes cannot be deserialized.
    let random_bytes = &cbor_cid[10..];
    let decoded_random_bytes: Result<Kinded, _> = from_slice(random_bytes);
    assert!(decoded_random_bytes.is_err());
}

#[test]
fn test_cid_in_tagged_enum() {
    #[derive(Debug, Deserialize, PartialEq)]
    pub enum Externally {
        Cid(Cid),
    }

    #[derive(Debug, Deserialize, PartialEq)]
    #[serde(tag = "type")]
    pub enum Internally {
        Cid { cid: Cid },
    }

    #[derive(Debug, Deserialize, PartialEq)]
    #[serde(untagged)]
    pub enum Untagged {
        Cid(Cid),
    }

    let cbor_cid = [
        0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x55, 0x12, 0x20, 0x2c, 0x26, 0xb4, 0x6b, 0x68, 0xff,
        0xc6, 0x8f, 0xf9, 0x9b, 0x45, 0x3c, 0x1d, 0x30, 0x41, 0x34, 0x13, 0x42, 0x2d, 0x70, 0x64,
        0x83, 0xbf, 0xa0, 0xf9, 0x8a, 0x5e, 0x88, 0x62, 0x66, 0xe7, 0xae,
    ];

    // {"Cid": cid}
    let cbor_map1 = [vec![0xa1, 0x63, 0x43, 0x69, 0x64], Vec::from(cbor_cid)].concat();

    // {"cid": cid, "type": "Cid"}
    let cbor_map2 = [
        vec![
            0xa2, 0x64, 0x74, 0x79, 0x70, 0x65, 0x63, 0x43, 0x69, 0x64, 0x63, 0x63, 0x69, 0x64,
        ],
        Vec::from(cbor_cid),
    ]
    .concat();

    let cid = Cid::from_bytes_raw(&cbor_cid[5..]).unwrap();

    let decoded: Externally = from_slice(&cbor_map1).unwrap();
    assert_eq!(decoded, Externally::Cid(cid));

    let decoded: Internally = from_slice(&cbor_map2).unwrap();
    assert_eq!(decoded, Internally::Cid { cid });

    let decoded: Untagged = from_slice(&cbor_cid).unwrap();
    assert_eq!(decoded, Untagged::Cid(cid));
}

#[test]
fn test_cid_empty_errors() {
    // Tag 42 with zero bytes
    let cbor_empty_cid = [0xd8, 0x2a, 0x40];

    let decoded: Result<Cid, _> = from_slice(&cbor_empty_cid);
    assert!(decoded.is_err());
}

#[test]
fn test_cid_decode_from_reader() {
    let cid_encoded = hex::decode(
        "d82a582500015512202c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    )
    .unwrap();

    let cid_decoded: Cid = from_slice(&cid_encoded).unwrap();
    assert_eq!(&cid_encoded[5..], cid_decoded.as_bytes());
}