diff --git a/README.md b/README.md index 11e171d..b296cf8 100644 --- a/README.md +++ b/README.md @@ -565,6 +565,7 @@ impl FromBencode for Location { fn main() {} +#[test] fn decode_list() -> Result<(), Error> { let encoded = b"li2ei3ee".to_vec(); let expected = Location(2, 3); diff --git a/examples/decode_torrent.rs b/examples/decode_torrent.rs index c20a850..3960a89 100644 --- a/examples/decode_torrent.rs +++ b/examples/decode_torrent.rs @@ -13,6 +13,7 @@ //! ``` //! cargo run --example decode_torrent > parsing_output.txt //! ``` + use bendy::{ decoding::{Error, FromBencode, Object, ResultExt}, encoding::AsString, diff --git a/rustfmt.toml b/rustfmt.toml index 0e6b31a..313fa0b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,6 @@ unstable_features = true -required_version = "1.3.0" +required_version = "1.4.6" edition = "2018" format_code_in_doc_comments = true diff --git a/src/lib.rs b/src/lib.rs index 2bc4a3c..64b0722 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,3 @@ extern crate alloc; pub mod decoding; pub mod encoding; pub mod state_tracker; - -#[cfg(test)] -doc_comment::doctest!("../README.md"); diff --git a/tests/readme.rs b/tests/readme.rs new file mode 100644 index 0000000..35b342f --- /dev/null +++ b/tests/readme.rs @@ -0,0 +1,365 @@ +// Please keep the code below in sync with `README.md`. +// +// If `cfg(doctest)` gets stablized or `cfg(test)` gets fixed, we can use +// doc-comment for running tests in `README.md`. + +mod encoding_1 { + use bendy::encoding::{Error, ToBencode}; + + #[test] + fn encode_vector() -> Result<(), Error> { + let my_data = vec!["hello", "world"]; + let encoded = my_data.to_bencode()?; + + assert_eq!(b"l5:hello5:worlde", encoded.as_slice()); + Ok(()) + } +} + +mod encoding_2 { + use bendy::encoding::{Error, SingleItemEncoder, ToBencode}; + + struct IntegerWrapper(i64); + + impl ToBencode for IntegerWrapper { + const MAX_DEPTH: usize = 0; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> { + encoder.emit_int(self.0) + } + } + + #[test] + fn encode_integer() -> Result<(), Error> { + let example = IntegerWrapper(21); + + let encoded = example.to_bencode()?; + assert_eq!(b"i21e", encoded.as_slice()); + + let encoded = 21.to_bencode()?; + assert_eq!(b"i21e", encoded.as_slice()); + + Ok(()) + } +} + +mod encoding_3 { + use bendy::encoding::{Error, SingleItemEncoder, ToBencode}; + + struct StringWrapper(String); + + impl ToBencode for StringWrapper { + const MAX_DEPTH: usize = 0; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> { + encoder.emit_str(&self.0) + } + } + + #[test] + fn encode_string() -> Result<(), Error> { + let example = StringWrapper("content".to_string()); + + let encoded = example.to_bencode()?; + assert_eq!(b"7:content", encoded.as_slice()); + + let encoded = "content".to_bencode()?; + assert_eq!(b"7:content", encoded.as_slice()); + + Ok(()) + } +} + +mod encoding_4 { + use bendy::encoding::{AsString, Error, SingleItemEncoder, ToBencode}; + + struct ByteStringWrapper(Vec); + + impl ToBencode for ByteStringWrapper { + const MAX_DEPTH: usize = 0; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> { + let content = AsString(&self.0); + encoder.emit(&content) + } + } + + #[test] + fn encode_byte_string() -> Result<(), Error> { + let example = ByteStringWrapper(b"content".to_vec()); + + let encoded = example.to_bencode()?; + assert_eq!(b"7:content", encoded.as_slice()); + + let encoded = AsString(b"content").to_bencode()?; + assert_eq!(b"7:content", encoded.as_slice()); + + Ok(()) + } +} + +mod encoding_5 { + use bendy::encoding::{Error, SingleItemEncoder, ToBencode}; + + struct Example { + label: String, + counter: u64, + } + + impl ToBencode for Example { + const MAX_DEPTH: usize = 1; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> { + encoder.emit_dict(|mut e| { + e.emit_pair(b"counter", &self.counter)?; + e.emit_pair(b"label", &self.label)?; + + Ok(()) + }) + } + } + + #[test] + fn encode_dictionary() -> Result<(), Error> { + let example = Example { + label: "Example".to_string(), + counter: 0, + }; + + let encoded = example.to_bencode()?; + assert_eq!(b"d7:counteri0e5:label7:Examplee", encoded.as_slice()); + + Ok(()) + } +} + +mod encoding_6 { + use bendy::encoding::{Error, SingleItemEncoder, ToBencode}; + + struct Location(i64, i64); + + impl ToBencode for Location { + const MAX_DEPTH: usize = 1; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> { + encoder.emit_list(|e| { + e.emit_int(self.0)?; + e.emit_int(self.1) + }) + } + } + + #[test] + fn encode_list() -> Result<(), Error> { + let example = Location(2, 3); + + let encoded = example.to_bencode()?; + assert_eq!(b"li2ei3ee", encoded.as_slice()); + + Ok(()) + } +} + +mod decoding_1 { + use bendy::decoding::{Error, FromBencode}; + + #[test] + fn decode_vector() -> Result<(), Error> { + let encoded = b"l5:hello5:worlde".to_vec(); + let decoded = Vec::::from_bencode(&encoded)?; + + assert_eq!(vec!["hello", "world"], decoded); + Ok(()) + } +} + +mod decoding_2 { + use bendy::decoding::{Error, FromBencode, Object}; + + #[derive(Debug, Eq, PartialEq)] + struct IntegerWrapper(i64); + + impl FromBencode for IntegerWrapper { + const EXPECTED_RECURSION_DEPTH: usize = 0; + + fn decode_bencode_object(object: Object) -> Result { + // This is an example for content handling. It would also be possible + // to call `i64::decode_bencode_object(object)` directly. + let content = object.try_into_integer()?; + let number = content.parse::()?; + + Ok(IntegerWrapper(number)) + } + } + + #[test] + fn decode_integer() -> Result<(), Error> { + let encoded = b"i21e".to_vec(); + + let example = IntegerWrapper::from_bencode(&encoded)?; + assert_eq!(IntegerWrapper(21), example); + + let example = i64::from_bencode(&encoded)?; + assert_eq!(21, example); + + Ok(()) + } +} + +mod decoding_3 { + use bendy::decoding::{Error, FromBencode, Object}; + + #[derive(Debug, Eq, PartialEq)] + struct StringWrapper(String); + + impl FromBencode for StringWrapper { + const EXPECTED_RECURSION_DEPTH: usize = 0; + + fn decode_bencode_object(object: Object) -> Result { + // This is an example for content handling. It would also be possible + // to call `String::decode_bencode_object(object)` directly. + let content = object.try_into_bytes()?; + let content = String::from_utf8(content.to_vec())?; + + Ok(StringWrapper(content)) + } + } + + #[test] + fn decode_string() -> Result<(), Error> { + let encoded = b"7:content".to_vec(); + + let example = StringWrapper::from_bencode(&encoded)?; + assert_eq!(StringWrapper("content".to_string()), example); + + let example = String::from_bencode(&encoded)?; + assert_eq!("content".to_string(), example); + + Ok(()) + } +} + +mod decoding_4 { + use bendy::{ + decoding::{Error, FromBencode, Object}, + encoding::AsString, + }; + + #[derive(Debug, Eq, PartialEq)] + struct ByteStringWrapper(Vec); + + impl FromBencode for ByteStringWrapper { + const EXPECTED_RECURSION_DEPTH: usize = 0; + + fn decode_bencode_object(object: Object) -> Result { + let content = AsString::decode_bencode_object(object)?; + Ok(ByteStringWrapper(content.0)) + } + } + + #[test] + fn decode_byte_string() -> Result<(), Error> { + let encoded = b"7:content".to_vec(); + + let example = ByteStringWrapper::from_bencode(&encoded)?; + assert_eq!(ByteStringWrapper(b"content".to_vec()), example); + + let example = AsString::from_bencode(&encoded)?; + assert_eq!(b"content".to_vec(), example.0); + + Ok(()) + } +} + +mod decoding_5 { + use bendy::decoding::{Error, FromBencode, Object, ResultExt}; + + #[derive(Debug, Eq, PartialEq)] + struct Example { + label: String, + counter: u64, + } + + impl FromBencode for Example { + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result { + let mut counter = None; + let mut label = None; + + let mut dict = object.try_into_dictionary()?; + while let Some(pair) = dict.next_pair()? { + match pair { + (b"counter", value) => { + counter = u64::decode_bencode_object(value) + .context("counter") + .map(Some)?; + }, + (b"label", value) => { + label = String::decode_bencode_object(value) + .context("label") + .map(Some)?; + }, + (unknown_field, _) => { + return Err(Error::unexpected_field(String::from_utf8_lossy( + unknown_field, + ))); + }, + } + } + + let counter = counter.ok_or_else(|| Error::missing_field("counter"))?; + let label = label.ok_or_else(|| Error::missing_field("label"))?; + + Ok(Example { counter, label }) + } + } + + #[test] + fn decode_dictionary() -> Result<(), Error> { + let encoded = b"d7:counteri0e5:label7:Examplee".to_vec(); + let expected = Example { + label: "Example".to_string(), + counter: 0, + }; + + let example = Example::from_bencode(&encoded)?; + assert_eq!(expected, example); + + Ok(()) + } +} + +mod decoding_6 { + use bendy::decoding::{Error, FromBencode, Object}; + + #[derive(Debug, PartialEq, Eq)] + struct Location(i64, i64); + + impl FromBencode for Location { + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result { + let mut list = object.try_into_list()?; + + let x = list.next_object()?.ok_or(Error::missing_field("x"))?; + let x = i64::decode_bencode_object(x)?; + + let y = list.next_object()?.ok_or(Error::missing_field("y"))?; + let y = i64::decode_bencode_object(y)?; + + Ok(Location(x, y)) + } + } + + #[test] + fn decode_list() -> Result<(), Error> { + let encoded = b"li2ei3ee".to_vec(); + let expected = Location(2, 3); + + let example = Location::from_bencode(&encoded)?; + assert_eq!(expected, example); + + Ok(()) + } +}