1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! Library for encoding Rust program data into a binary stream, and decoding that stream.
//!
//! Example:
//! ```
//! # use bytes::Bytes;
//! # use futures::executor::block_on;
//! let expected = ("one".to_string(), 2.0, vec![3, 4], Bytes::from(vec![5u8]));
//! let stream = tbon::en::encode(&expected).unwrap();
//! let actual = block_on(tbon::de::try_decode((), stream)).unwrap();
//! assert_eq!(expected, actual);
//! ```
//!
//! *Important note*: TBON adds one byte to primitive values to record the value's type. In cases
//! where TBON must encode a large number of values of the same type, such as an n-dimensional array
//! of numbers, this adds 12-50% overhead to the size of the encoded data. However, encoding a
//! `Bytes` struct has a negligible overhead of only two bytes, regardless of the data size.
//!
//! To efficiently encode an n-dimensional array, it is recommended to use compression
//! (e.g. gzip) and/or implement [`destream::FromStream`] and [`destream::ToStream`] using `Bytes`.
//!

mod constants;

pub mod de;
pub mod en;

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::fmt;
    use std::iter::FromIterator;

    use bytes::Bytes;
    use destream::{FromStream, IntoStream};

    use rand::Rng;

    use super::de::*;
    use super::en::*;

    async fn run_test<
        'en,
        T: FromStream<Context = ()> + IntoStream<'en> + fmt::Debug + PartialEq + Clone + 'en,
    >(
        value: T,
    ) {
        let encoded = encode(value.clone()).unwrap();
        let decoded: T = try_decode((), encoded).await.unwrap();
        assert_eq!(decoded, value);
    }

    #[tokio::test]
    async fn test_primitives() {
        run_test(true).await;
        run_test(false).await;

        for u in 0..66000u64 {
            run_test(u).await;
        }

        for i in -66000..66000i64 {
            run_test(i).await;
        }

        for _ in 0..100000 {
            let f: f32 = rand::thread_rng().gen();
            run_test(f).await;
        }
    }

    #[tokio::test]
    async fn test_strings() {
        run_test(String::from("hello world")).await;
        run_test(String::from("Привет, мир")).await;
        run_test(String::from("this is a \"string\" within a \\ string")).await;
        run_test(String::from("this \"string\" is \\\terminated by a \\")).await;

        let bitstring = Bytes::from((0..255u8).collect::<Vec<u8>>());
        run_test(bitstring).await;
    }

    #[tokio::test]
    async fn test_compound() {
        let list = vec![String::from("hello"), String::from("world")];
        run_test(list).await;

        let tuple = (
            true,
            -1i16,
            3.14,
            String::from(" hello \"world\""),
            Bytes::from((0..255u8).collect::<Vec<u8>>()),
        );
        run_test(tuple).await;

        let mut map = HashMap::new();
        map.insert(-1i32, String::from("I'm a teapot"));
        map.insert(-1i32, String::from("\' \"\"     "));
        run_test(map).await;

        let tuple: (Vec<f32>, Vec<i32>) = (vec![], vec![1]);
        run_test(tuple).await;

        let mut map = HashMap::new();
        map.insert("one".to_string(), HashMap::new());
        map.insert(
            "two".to_string(),
            HashMap::from_iter(vec![("three".to_string(), 4f32)]),
        );
        run_test(map).await;
    }
}