b3-rs 0.1.0

A Rust implementation of B3 (Better Binary Buffers)
//! Item structures, and encoding/decoding helpers.

use crate::alloc_prelude::*;
use crate::datatypes::*;
use crate::{ItemKey, ItemValue, ItemHeader};

/// A singular item.
#[derive(Debug, Clone, PartialEq)]
pub struct Item {
    pub key: ItemKey,
    pub value: ItemValue,
}

impl Item {
    /// Create a new item with the given key and value.
    pub fn new(key: ItemKey, value: ItemValue) -> Self {
        Self { key, value }
    }

    /// Add a key to the item, returning the item. Useful for chaining.
    ///
    /// ```
    /// # use b3_rs::*;
    /// let item = Item::from("value").with_key(ItemKey::from("key"));
    /// ```
    pub fn with_key(mut self, key: ItemKey) -> Self {
        self.key = key;
        self
    }

    /// Encode this item into it's byte representation.
    ///
    /// Internally this calls the `encode()` method on the [`ItemValue`],
    /// constructs an [`ItemHeader`] based on the output from that, encodes
    /// that header, and stitches the two together.
    pub fn encode(&self) -> Result<Vec<u8>, crate::Error> {
        // Encode value
        let (data_type, mut value_enc) = self.value.encode()?;

        // Force empty data if we're a null value
        if data_type == DataType::Null {
            value_enc.clear();
        }

        // Construct header for encoded value
        let header = ItemHeader::new(
            data_type,
            self.key.clone(),
            data_type == DataType::Null,
            value_enc.len() as u64,
        );

        // Encode header
        let header_enc = header.encode()?;

        // Construct output
        let mut out: Vec<u8> = Vec::new();
        out.extend(&header_enc);
        out.extend(&value_enc);

        Ok(out)
    }

    /// Decodes an item from the given byte input.
    ///
    /// If the item is a container type (`CompositeList` or `CompositeDict`),
    /// the item is decoded recursively.
    ///
    /// If decoding was successful, returns the item, and the number of bytes
    /// consumed by this decode operation.
    pub fn decode(input: &[u8]) -> Result<(Item, usize), crate::Error> {
        let mut count: usize = 0;

        // Decode the header
        let (hdr, hdr_count) = ItemHeader::decode(&input[count..])?;
        count += hdr_count;

        // Decode the value
        let (val, val_count) = ItemValue::decode(&hdr, &input[count..])?;
        count += val_count;

        // Construct `Item`
        let item = Item::new(hdr.key, val);

        // Return success
        Ok((item, count))
    }
}

impl From<Vec<u8>> for Item {
    fn from(v: Vec<u8>) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::Bytes(v))
    }
}

impl From<&[u8]> for Item {
    fn from(v: &[u8]) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::Bytes(Vec::from(v)))
    }
}

impl From<String> for Item {
    fn from(s: String) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::UTF8(s))
    }
}

impl From<&str> for Item {
    fn from(s: &str) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::UTF8(String::from(s)))
    }
}

impl From<bool> for Item {
    fn from(b: bool) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::Boolean(b))
    }
}

// Policy: Use the LEB128 message types instead of the fixed-size Int64 when
// implementing From<> etc for integer types. Floating point is an exception,
// because we can't represent an f64 in LEB128.

impl From<u32> for Item {
    fn from(i: u32) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::UVarInt(i as u64))
    }
}

impl From<u64> for Item {
    fn from(i: u64) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::UVarInt(i))
    }
}

impl From<i32> for Item {
    fn from(i: i32) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::SVarInt(i as i64))
    }
}

impl From<i64> for Item {
    fn from(i: i64) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::SVarInt(i))
    }
}

impl From<f64> for Item {
    fn from(i: f64) -> Item {
        Item::new(ItemKey::NoKey, ItemValue::Float64(i))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encode_boolean_true() {
        let item = Item::new(
            ItemKey::StringKey(String::from("A")),
            ItemValue::Boolean(true),
        );
        let out = item.encode().unwrap();

        assert_eq!(out, vec![0b01100101, 0x01, 0x41, 0x01, 0x01]);
    }

    #[test]
    fn decode_boolean_true() {
        let data = vec![0b01100101, 0x01, 0x41, 0x01, 0x01];
        let (item, count) = Item::decode(&data).unwrap();

        assert_eq!(count, data.len());
        assert_eq!(item.key, ItemKey::StringKey(String::from("A")));
        assert_eq!(item.value, ItemValue::Boolean(true));
    }

    #[test]
    fn encode_composite_list_recursive() {
        let val = Item::new(ItemKey::NoKey, ItemValue::Boolean(true));
        let l_val = vec![val.clone(), val.clone()];
        let inner = Item::new(ItemKey::NoKey, ItemValue::CompositeList(l_val));
        let l_inner = vec![inner.clone(), inner.clone()];
        let item = Item::new(ItemKey::NoKey, ItemValue::CompositeList(l_inner));

        let res = item.encode().unwrap();
        assert_eq!(
            res,
            vec![
                0b01000010, 0x10, // outer container
                0b01000010, 0x06, // inner container
                0b01000101, 0x01, 0x01, // inner boolean
                0b01000101, 0x01, 0x01, // inner boolean
                0b01000010, 0x06, // inner container
                0b01000101, 0x01, 0x01, // inner boolean
                0b01000101, 0x01, 0x01, // inner boolean
            ]
        );
    }

    #[test]
    fn decode_composite_list_recursive() {
        let data = vec![
            0b01000010, 0x10, // outer container
            0b01000010, 0x06, // inner container
            0b01000101, 0x01, 0x01, // inner boolean
            0b01000101, 0x01, 0x01, // inner boolean
            0b01000010, 0x06, // inner container
            0b01000101, 0x01, 0x01, // inner boolean
            0b01000101, 0x01, 0x01, // inner boolean
        ];

        let (item, count) = Item::decode(&data).unwrap();
        assert_eq!(count, data.len());

        // Outer - CompositeList with two items
        let outer = item.value.get_entries().unwrap();
        assert_eq!(outer.len(), 2);

        // Middle - CompositeList with two items
        let mid = outer[0].value.get_entries().unwrap();
        assert_eq!(mid.len(), 2);

        // Inner - Boolean 'true'
        let inner = mid[0].value.get_bool();
        assert_eq!(inner, Some(true));
    }

    #[test]
    fn encode_composite_dict_recursive() {
        let val = Item::new(ItemKey::IntegerKey(2), ItemValue::Boolean(true));
        let l_val = vec![val.clone(), val.clone()];
        let inner = Item::new(ItemKey::IntegerKey(1), ItemValue::CompositeDict(l_val));
        let l_inner = vec![inner.clone(), inner.clone()];
        let item = Item::new(ItemKey::NoKey, ItemValue::CompositeDict(l_inner));

        let res = item.encode().unwrap();
        assert_eq!(
            res,
            vec![
                0b01000001, 0x16, // outer container
                0b01010001, 0x01, 0x08, // inner container
                0b01010101, 0x02, 0x01, 0x01, // inner boolean
                0b01010101, 0x02, 0x01, 0x01, // inner boolean
                0b01010001, 0x01, 0x08, // inner container
                0b01010101, 0x02, 0x01, 0x01, // inner boolean
                0b01010101, 0x02, 0x01, 0x01, // inner boolean
            ]
        );
    }

    #[test]
    fn decode_composite_dict_recursive() {
        let data = vec![
            0b01000001, 0x16, // outer container
            0b01010001, 0x01, 0x08, // inner container
            0b01010101, 0x02, 0x01, 0x01, // inner boolean
            0b01010101, 0x02, 0x01, 0x01, // inner boolean
            0b01010001, 0x01, 0x08, // inner container
            0b01010101, 0x02, 0x01, 0x01, // inner boolean
            0b01010101, 0x02, 0x01, 0x01, // inner boolean
        ];

        let (item, count) = Item::decode(&data).unwrap();
        assert_eq!(count, data.len());

        // Outer - CompositeDict with two items
        let outer = item.value.get_entries().unwrap();
        assert_eq!(outer.len(), 2);

        // Middle - CompositeDict with two items
        let mid = outer[0].value.get_entries().unwrap();
        assert_eq!(mid.len(), 2);

        // Inner - Boolean 'true'
        let inner = mid[0].value.get_bool();
        assert_eq!(inner, Some(true));
    }
}