diff options
author | DongHun Kwak <dh0128.kwak@samsung.com> | 2023-04-26 15:25:39 +0900 |
---|---|---|
committer | DongHun Kwak <dh0128.kwak@samsung.com> | 2023-04-26 15:25:39 +0900 |
commit | 17a5b5d427ccda706b7ccf57af1cd5c04ced079b (patch) | |
tree | b8842b5f8510632159eb281386063e412e0c2b8a | |
download | rust-serde_bytes-upstream.tar.gz rust-serde_bytes-upstream.tar.bz2 rust-serde_bytes-upstream.zip |
Import serde_bytes 0.11.9upstream/0.11.9upstream
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 52 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.toml | 56 | ||||
-rw-r--r-- | Cargo.toml.orig | 31 | ||||
-rw-r--r-- | LICENSE-APACHE | 176 | ||||
-rw-r--r-- | LICENSE-MIT | 23 | ||||
-rw-r--r-- | README.md | 54 | ||||
-rw-r--r-- | src/bytebuf.rs | 253 | ||||
-rw-r--r-- | src/bytes.rs | 172 | ||||
-rw-r--r-- | src/de.rs | 229 | ||||
-rw-r--r-- | src/lib.rs | 113 | ||||
-rw-r--r-- | src/ser.rs | 136 | ||||
-rw-r--r-- | tests/test_derive.rs | 101 | ||||
-rw-r--r-- | tests/test_partialeq.rs | 15 | ||||
-rw-r--r-- | tests/test_serde.rs | 59 |
16 files changed, 1479 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..555ba05 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "c3c83935d3943224c17ab11a7796db8e2810b7dc" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..66b119c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + pull_request: + schedule: [cron: "40 1 * * *"] + +permissions: + contents: read + +env: + RUSTFLAGS: -Dwarnings + +jobs: + test: + name: Rust ${{matrix.rust}} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + rust: [nightly, beta, stable, 1.36.0, 1.34.0, 1.31.0] + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{matrix.rust}} + - run: cargo test + if: matrix.rust != '1.31.0' && matrix.rust != '1.34.0' + - run: cargo check --no-default-features + - run: cargo check --no-default-features --features alloc + if: matrix.rust != '1.31.0' && matrix.rust != '1.34.0' + + clippy: + name: Clippy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@clippy + - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic + + outdated: + name: Outdated + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/install@cargo-outdated + - run: cargo outdated --workspace --exit-code 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4308d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2e75f8a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,56 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.31" +name = "serde_bytes" +version = "0.11.9" +authors = ["David Tolnay <dtolnay@gmail.com>"] +description = "Optimized handling of `&[u8]` and `Vec<u8>` for Serde" +documentation = "https://docs.rs/serde_bytes" +readme = "README.md" +keywords = [ + "serde", + "serialization", + "no_std", + "bytes", +] +categories = [ + "encoding", + "no-std", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/serde-rs/bytes" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +doc-scrape-examples = false + +[dependencies.serde] +version = "1.0" +default-features = false + +[dev-dependencies.bincode] +version = "1.0" + +[dev-dependencies.serde_derive] +version = "1.0" + +[dev-dependencies.serde_test] +version = "1.0" + +[features] +alloc = ["serde/alloc"] +default = ["std"] +std = ["serde/std"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..241a337 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,31 @@ +[package] +name = "serde_bytes" +version = "0.11.9" # remember to update html_root_url +authors = ["David Tolnay <dtolnay@gmail.com>"] +categories = ["encoding", "no-std"] +description = "Optimized handling of `&[u8]` and `Vec<u8>` for Serde" +documentation = "https://docs.rs/serde_bytes" +edition = "2018" +keywords = ["serde", "serialization", "no_std", "bytes"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/serde-rs/bytes" +rust-version = "1.31" + +[features] +default = ["std"] +std = ["serde/std"] +alloc = ["serde/alloc"] + +[dependencies] +serde = { version = "1.0", default-features = false } + +[dev-dependencies] +bincode = "1.0" +serde_derive = "1.0" +serde_test = "1.0" + +[lib] +doc-scrape-examples = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0a0e9f --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# serde\_bytes [![Build Status](https://img.shields.io/github/actions/workflow/status/serde-rs/bytes/ci.yml?branch=master)](https://github.com/serde-rs/bytes/actions?query=branch%3Amaster) [![Latest Version](https://img.shields.io/crates/v/serde_bytes.svg)](https://crates.io/crates/serde_bytes) + +Wrapper types to enable optimized handling of `&[u8]` and `Vec<u8>`. + +```toml +[dependencies] +serde_bytes = "0.11" +``` + +## Explanation + +Without specialization, Rust forces Serde to treat `&[u8]` just like any +other slice and `Vec<u8>` just like any other vector. In reality this +particular slice and vector can often be serialized and deserialized in a +more efficient, compact representation in many formats. + +When working with such a format, you can opt into specialized handling of +`&[u8]` by wrapping it in `serde_bytes::Bytes` and `Vec<u8>` by wrapping it +in `serde_bytes::ByteBuf`. + +Additionally this crate supports the Serde `with` attribute to enable efficient +handling of `&[u8]` and `Vec<u8>` in structs without needing a wrapper type. + +## Example + +```rust +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +struct Efficient<'a> { + #[serde(with = "serde_bytes")] + bytes: &'a [u8], + + #[serde(with = "serde_bytes")] + byte_buf: Vec<u8>, +} +``` + +<br> + +#### License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> + +<br> + +<sub> +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. +</sub> diff --git a/src/bytebuf.rs b/src/bytebuf.rs new file mode 100644 index 0000000..0c2daa6 --- /dev/null +++ b/src/bytebuf.rs @@ -0,0 +1,253 @@ +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::{self, Ordering}; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +use crate::Bytes; + +/// Wrapper around `Vec<u8>` to serialize and deserialize efficiently. +/// +/// ``` +/// use std::collections::HashMap; +/// use std::io; +/// +/// use serde_bytes::ByteBuf; +/// +/// fn deserialize_bytebufs() -> bincode::Result<()> { +/// let example_data = [ +/// 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 116, +/// 119, 111, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 111, 110, 101]; +/// +/// let map: HashMap<u32, ByteBuf> = bincode::deserialize(&example_data[..])?; +/// +/// println!("{:?}", map); +/// +/// Ok(()) +/// } +/// # +/// # fn main() { +/// # deserialize_bytebufs().unwrap(); +/// # } +/// ``` +#[derive(Clone, Default, Eq, Ord)] +pub struct ByteBuf { + bytes: Vec<u8>, +} + +impl ByteBuf { + /// Construct a new, empty `ByteBuf`. + pub fn new() -> Self { + ByteBuf::from(Vec::new()) + } + + /// Construct a new, empty `ByteBuf` with the specified capacity. + pub fn with_capacity(cap: usize) -> Self { + ByteBuf::from(Vec::with_capacity(cap)) + } + + /// Wrap existing bytes in a `ByteBuf`. + pub fn from<T: Into<Vec<u8>>>(bytes: T) -> Self { + ByteBuf { + bytes: bytes.into(), + } + } + + /// Unwrap the vector of byte underlying this `ByteBuf`. + pub fn into_vec(self) -> Vec<u8> { + self.bytes + } + + #[allow(missing_docs)] + pub fn into_boxed_bytes(self) -> Box<Bytes> { + self.bytes.into_boxed_slice().into() + } + + // This would hit "cannot move out of borrowed content" if invoked through + // the Deref impl; make it just work. + #[doc(hidden)] + pub fn into_boxed_slice(self) -> Box<[u8]> { + self.bytes.into_boxed_slice() + } + + #[doc(hidden)] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> <Vec<u8> as IntoIterator>::IntoIter { + self.bytes.into_iter() + } +} + +impl Debug for ByteBuf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.bytes, f) + } +} + +impl AsRef<[u8]> for ByteBuf { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl AsMut<[u8]> for ByteBuf { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +impl Deref for ByteBuf { + type Target = Vec<u8>; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl DerefMut for ByteBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + +impl Borrow<Bytes> for ByteBuf { + fn borrow(&self) -> &Bytes { + Bytes::new(&self.bytes) + } +} + +impl BorrowMut<Bytes> for ByteBuf { + fn borrow_mut(&mut self) -> &mut Bytes { + unsafe { &mut *(&mut self.bytes as &mut [u8] as *mut [u8] as *mut Bytes) } + } +} + +impl<Rhs> PartialEq<Rhs> for ByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn eq(&self, other: &Rhs) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<Rhs> PartialOrd<Rhs> for ByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn partial_cmp(&self, other: &Rhs) -> Option<Ordering> { + self.as_ref().partial_cmp(other.as_ref()) + } +} + +impl Hash for ByteBuf { + fn hash<H: Hasher>(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl IntoIterator for ByteBuf { + type Item = u8; + type IntoIter = <Vec<u8> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.into_iter() + } +} + +impl<'a> IntoIterator for &'a ByteBuf { + type Item = &'a u8; + type IntoIter = <&'a [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter() + } +} + +impl<'a> IntoIterator for &'a mut ByteBuf { + type Item = &'a mut u8; + type IntoIter = <&'a mut [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter_mut() + } +} + +impl Serialize for ByteBuf { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(&self.bytes) + } +} + +struct ByteBufVisitor; + +impl<'de> Visitor<'de> for ByteBufVisitor { + type Value = ByteBuf; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("byte array") + } + + fn visit_seq<V>(self, mut visitor: V) -> Result<ByteBuf, V::Error> + where + V: SeqAccess<'de>, + { + let len = cmp::min(visitor.size_hint().unwrap_or(0), 4096); + let mut bytes = Vec::with_capacity(len); + + while let Some(b) = visitor.next_element()? { + bytes.push(b); + } + + Ok(ByteBuf::from(bytes)) + } + + fn visit_bytes<E>(self, v: &[u8]) -> Result<ByteBuf, E> + where + E: Error, + { + Ok(ByteBuf::from(v)) + } + + fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<ByteBuf, E> + where + E: Error, + { + Ok(ByteBuf::from(v)) + } + + fn visit_str<E>(self, v: &str) -> Result<ByteBuf, E> + where + E: Error, + { + Ok(ByteBuf::from(v)) + } + + fn visit_string<E>(self, v: String) -> Result<ByteBuf, E> + where + E: Error, + { + Ok(ByteBuf::from(v)) + } +} + +impl<'de> Deserialize<'de> for ByteBuf { + fn deserialize<D>(deserializer: D) -> Result<ByteBuf, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_byte_buf(ByteBufVisitor) + } +} diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 0000000..ad0902b --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,172 @@ +use core::cmp::Ordering; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; + +#[cfg(feature = "alloc")] +use alloc::borrow::ToOwned; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(any(feature = "std", feature = "alloc"))] +use crate::ByteBuf; + +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; + +/// Wrapper around `[u8]` to serialize and deserialize efficiently. +/// +/// ``` +/// use std::collections::HashMap; +/// use std::io; +/// +/// use serde_bytes::Bytes; +/// +/// fn print_encoded_cache() -> bincode::Result<()> { +/// let mut cache = HashMap::new(); +/// cache.insert(3, Bytes::new(b"three")); +/// cache.insert(2, Bytes::new(b"two")); +/// cache.insert(1, Bytes::new(b"one")); +/// +/// bincode::serialize_into(&mut io::stdout(), &cache) +/// } +/// # +/// # fn main() { +/// # print_encoded_cache().unwrap(); +/// # } +/// ``` +#[derive(Eq, Ord)] +#[cfg_attr(not(doc), repr(transparent))] +pub struct Bytes { + bytes: [u8], +} + +impl Bytes { + /// Wrap an existing `&[u8]`. + pub fn new(bytes: &[u8]) -> &Self { + unsafe { &*(bytes as *const [u8] as *const Bytes) } + } +} + +impl Debug for Bytes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.bytes, f) + } +} + +impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl AsMut<[u8]> for Bytes { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +impl Deref for Bytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl DerefMut for Bytes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl ToOwned for Bytes { + type Owned = ByteBuf; + + fn to_owned(&self) -> Self::Owned { + ByteBuf::from(&self.bytes) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl From<Box<[u8]>> for Box<Bytes> { + fn from(bytes: Box<[u8]>) -> Self { + unsafe { Box::from_raw(Box::into_raw(bytes) as *mut Bytes) } + } +} + +impl<'a> Default for &'a Bytes { + fn default() -> Self { + Bytes::new(&[]) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl Default for Box<Bytes> { + fn default() -> Self { + ByteBuf::new().into_boxed_bytes() + } +} + +impl<Rhs> PartialEq<Rhs> for Bytes +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn eq(&self, other: &Rhs) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<Rhs> PartialOrd<Rhs> for Bytes +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn partial_cmp(&self, other: &Rhs) -> Option<Ordering> { + self.as_ref().partial_cmp(other.as_ref()) + } +} + +impl Hash for Bytes { + fn hash<H: Hasher>(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl<'a> IntoIterator for &'a Bytes { + type Item = &'a u8; + type IntoIter = <&'a [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter() + } +} + +impl<'a> IntoIterator for &'a mut Bytes { + type Item = &'a mut u8; + type IntoIter = <&'a mut [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter_mut() + } +} + +impl Serialize for Bytes { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(&self.bytes) + } +} + +impl<'a, 'de: 'a> Deserialize<'de> for &'a Bytes { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + // serde::Deserialize for &[u8] is already optimized, so simply forward to that. + Deserialize::deserialize(deserializer).map(Bytes::new) + } +} diff --git a/src/de.rs b/src/de.rs new file mode 100644 index 0000000..e4a4732 --- /dev/null +++ b/src/de.rs @@ -0,0 +1,229 @@ +use crate::Bytes; +use core::fmt; +use core::marker::PhantomData; +use serde::de::{Error, Visitor}; +use serde::Deserializer; + +#[cfg(any(feature = "std", feature = "alloc"))] +use crate::ByteBuf; + +#[cfg(any(feature = "std", feature = "alloc"))] +use core::cmp; + +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; +#[cfg(all(feature = "std", not(feature = "alloc")))] +use std::borrow::Cow; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(any(feature = "std", feature = "alloc"))] +use serde::de::SeqAccess; + +/// Types that can be deserialized via `#[serde(with = "serde_bytes")]`. +pub trait Deserialize<'de>: Sized { + #[allow(missing_docs)] + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>; +} + +impl<'de: 'a, 'a> Deserialize<'de> for &'a [u8] { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + // serde::Deserialize for &[u8] is already optimized, so simply forward to that. + serde::Deserialize::deserialize(deserializer) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de> Deserialize<'de> for Vec<u8> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(ByteBuf::into_vec) + } +} + +impl<'de: 'a, 'a> Deserialize<'de> for &'a Bytes { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + // serde::Deserialize for &[u8] is already optimized, so simply forward to that. + serde::Deserialize::deserialize(deserializer).map(Bytes::new) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de> Deserialize<'de> for ByteBuf { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + // Via the serde::Deserialize impl for ByteBuf. + serde::Deserialize::deserialize(deserializer) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de: 'a, 'a> Deserialize<'de> for Cow<'a, [u8]> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct CowVisitor; + + impl<'de> Visitor<'de> for CowVisitor { + type Value = Cow<'de, [u8]>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Borrowed(v)) + } + + fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Borrowed(v.as_bytes())) + } + + fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Owned(v.to_vec())) + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Owned(v.as_bytes().to_vec())) + } + + fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Owned(v)) + } + + fn visit_string<E>(self, v: String) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Cow::Owned(v.into_bytes())) + } + + fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error> + where + V: SeqAccess<'de>, + { + let len = cmp::min(visitor.size_hint().unwrap_or(0), 4096); + let mut bytes = Vec::with_capacity(len); + + while let Some(b) = visitor.next_element()? { + bytes.push(b); + } + + Ok(Cow::Owned(bytes)) + } + } + + deserializer.deserialize_bytes(CowVisitor) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de: 'a, 'a> Deserialize<'de> for Cow<'a, Bytes> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let cow: Cow<[u8]> = Deserialize::deserialize(deserializer)?; + match cow { + Cow::Borrowed(bytes) => Ok(Cow::Borrowed(Bytes::new(bytes))), + Cow::Owned(bytes) => Ok(Cow::Owned(ByteBuf::from(bytes))), + } + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de> Deserialize<'de> for Box<[u8]> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(Vec::into_boxed_slice) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'de> Deserialize<'de> for Box<Bytes> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let bytes: Box<[u8]> = Deserialize::deserialize(deserializer)?; + Ok(bytes.into()) + } +} + +impl<'de, T> Deserialize<'de> for Option<T> +where + T: Deserialize<'de>, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct BytesVisitor<T> { + out: PhantomData<T>, + } + + impl<'de, T> Visitor<'de> for BytesVisitor<T> + where + T: Deserialize<'de>, + { + type Value = Option<T>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("optional byte array") + } + + fn visit_unit<E: Error>(self) -> Result<Self::Value, E> { + Ok(None) + } + + fn visit_none<E: Error>(self) -> Result<Self::Value, E> { + Ok(None) + } + + fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error> + where + D: Deserializer<'de>, + { + T::deserialize(deserializer).map(Some) + } + } + + let visitor = BytesVisitor { out: PhantomData }; + deserializer.deserialize_option(visitor) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..871d6cb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,113 @@ +//! Wrapper types to enable optimized handling of `&[u8]` and `Vec<u8>`. +//! +//! Without specialization, Rust forces Serde to treat `&[u8]` just like any +//! other slice and `Vec<u8>` just like any other vector. In reality this +//! particular slice and vector can often be serialized and deserialized in a +//! more efficient, compact representation in many formats. +//! +//! When working with such a format, you can opt into specialized handling of +//! `&[u8]` by wrapping it in `serde_bytes::Bytes` and `Vec<u8>` by wrapping it +//! in `serde_bytes::ByteBuf`. +//! +//! Additionally this crate supports the Serde `with` attribute to enable +//! efficient handling of `&[u8]` and `Vec<u8>` in structs without needing a +//! wrapper type. +//! +//! ``` +//! # use serde_derive::{Deserialize, Serialize}; +//! use serde::{Deserialize, Serialize}; +//! +//! #[derive(Deserialize, Serialize)] +//! struct Efficient<'a> { +//! #[serde(with = "serde_bytes")] +//! bytes: &'a [u8], +//! +//! #[serde(with = "serde_bytes")] +//! byte_buf: Vec<u8>, +//! } +//! ``` + +#![doc(html_root_url = "https://docs.rs/serde_bytes/0.11.9")] +#![cfg_attr(not(feature = "std"), no_std)] +#![deny(missing_docs)] +#![allow( + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::needless_doctest_main +)] + +mod bytes; +mod de; +mod ser; + +#[cfg(any(feature = "std", feature = "alloc"))] +mod bytebuf; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(any(feature = "std", feature = "alloc"))] +use serde::Deserializer; + +use serde::Serializer; + +pub use crate::bytes::Bytes; +pub use crate::de::Deserialize; +pub use crate::ser::Serialize; + +#[cfg(any(feature = "std", feature = "alloc"))] +pub use crate::bytebuf::ByteBuf; + +/// Serde `serialize_with` function to serialize bytes efficiently. +/// +/// This function can be used with either of the following Serde attributes: +/// +/// - `#[serde(with = "serde_bytes")]` +/// - `#[serde(serialize_with = "serde_bytes::serialize")]` +/// +/// ``` +/// # use serde_derive::Serialize; +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Efficient<'a> { +/// #[serde(with = "serde_bytes")] +/// bytes: &'a [u8], +/// +/// #[serde(with = "serde_bytes")] +/// byte_buf: Vec<u8>, +/// } +/// ``` +pub fn serialize<T, S>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error> +where + T: ?Sized + Serialize, + S: Serializer, +{ + Serialize::serialize(bytes, serializer) +} + +/// Serde `deserialize_with` function to deserialize bytes efficiently. +/// +/// This function can be used with either of the following Serde attributes: +/// +/// - `#[serde(with = "serde_bytes")]` +/// - `#[serde(deserialize_with = "serde_bytes::deserialize")]` +/// +/// ``` +/// # use serde_derive::Deserialize; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Packet { +/// #[serde(with = "serde_bytes")] +/// payload: Vec<u8>, +/// } +/// ``` +#[cfg(any(feature = "std", feature = "alloc"))] +pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error> +where + T: Deserialize<'de>, + D: Deserializer<'de>, +{ + Deserialize::deserialize(deserializer) +} diff --git a/src/ser.rs b/src/ser.rs new file mode 100644 index 0000000..1cbed9f --- /dev/null +++ b/src/ser.rs @@ -0,0 +1,136 @@ +use crate::Bytes; +use serde::Serializer; + +#[cfg(any(feature = "std", feature = "alloc"))] +use crate::ByteBuf; + +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; +#[cfg(all(feature = "std", not(feature = "alloc")))] +use std::borrow::Cow; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Types that can be serialized via `#[serde(with = "serde_bytes")]`. +pub trait Serialize { + #[allow(missing_docs)] + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer; +} + +impl Serialize for [u8] { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl Serialize for Vec<u8> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +impl Serialize for Bytes { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl Serialize for ByteBuf { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'a> Serialize for Cow<'a, [u8]> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'a> Serialize for Cow<'a, Bytes> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + +impl<'a, T> Serialize for &'a T +where + T: ?Sized + Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + (**self).serialize(serializer) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<T> Serialize for Box<T> +where + T: ?Sized + Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + (**self).serialize(serializer) + } +} + +impl<T> Serialize for Option<T> +where + T: Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + struct AsBytes<T>(T); + + impl<T> serde::Serialize for AsBytes<T> + where + T: Serialize, + { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + self.0.serialize(serializer) + } + } + + match self { + Some(b) => serializer.serialize_some(&AsBytes(b)), + None => serializer.serialize_none(), + } + } +} diff --git a/tests/test_derive.rs b/tests/test_derive.rs new file mode 100644 index 0000000..c7e97f1 --- /dev/null +++ b/tests/test_derive.rs @@ -0,0 +1,101 @@ +#![allow(clippy::derive_partial_eq_without_eq, clippy::ref_option_ref)] + +use serde_bytes::{ByteBuf, Bytes}; +use serde_derive::{Deserialize, Serialize}; +use serde_test::{assert_tokens, Token}; +use std::borrow::Cow; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct Test<'a> { + #[serde(with = "serde_bytes")] + slice: &'a [u8], + + #[serde(with = "serde_bytes")] + vec: Vec<u8>, + + #[serde(with = "serde_bytes")] + bytes: &'a Bytes, + + #[serde(with = "serde_bytes")] + byte_buf: ByteBuf, + + #[serde(with = "serde_bytes")] + cow_slice: Cow<'a, [u8]>, + + #[serde(with = "serde_bytes")] + cow_bytes: Cow<'a, Bytes>, + + #[serde(with = "serde_bytes")] + boxed_slice: Box<[u8]>, + + #[serde(with = "serde_bytes")] + boxed_bytes: Box<Bytes>, + + #[serde(with = "serde_bytes")] + opt_slice: Option<&'a [u8]>, + + #[serde(with = "serde_bytes")] + opt_vec: Option<Vec<u8>>, + + #[serde(with = "serde_bytes")] + opt_cow_slice: Option<Cow<'a, [u8]>>, +} + +#[derive(Serialize)] +struct Dst { + #[serde(with = "serde_bytes")] + bytes: [u8], +} + +#[test] +fn test() { + let test = Test { + slice: b"...", + vec: b"...".to_vec(), + bytes: Bytes::new(b"..."), + byte_buf: ByteBuf::from(b"...".as_ref()), + cow_slice: Cow::Borrowed(b"..."), + cow_bytes: Cow::Borrowed(Bytes::new(b"...")), + boxed_slice: b"...".to_vec().into_boxed_slice(), + boxed_bytes: ByteBuf::from(b"...".as_ref()).into_boxed_bytes(), + opt_slice: Some(b"..."), + opt_vec: Some(b"...".to_vec()), + opt_cow_slice: Some(Cow::Borrowed(b"...")), + }; + + assert_tokens( + &test, + &[ + Token::Struct { + name: "Test", + len: 11, + }, + Token::Str("slice"), + Token::BorrowedBytes(b"..."), + Token::Str("vec"), + Token::Bytes(b"..."), + Token::Str("bytes"), + Token::BorrowedBytes(b"..."), + Token::Str("byte_buf"), + Token::Bytes(b"..."), + Token::Str("cow_slice"), + Token::BorrowedBytes(b"..."), + Token::Str("cow_bytes"), + Token::BorrowedBytes(b"..."), + Token::Str("boxed_slice"), + Token::Bytes(b"..."), + Token::Str("boxed_bytes"), + Token::Bytes(b"..."), + Token::Str("opt_slice"), + Token::Some, + Token::BorrowedBytes(b"..."), + Token::Str("opt_vec"), + Token::Some, + Token::Bytes(b"..."), + Token::Str("opt_cow_slice"), + Token::Some, + Token::BorrowedBytes(b"..."), + Token::StructEnd, + ], + ); +} diff --git a/tests/test_partialeq.rs b/tests/test_partialeq.rs new file mode 100644 index 0000000..a57d194 --- /dev/null +++ b/tests/test_partialeq.rs @@ -0,0 +1,15 @@ +#![allow(clippy::needless_pass_by_value)] + +use serde_bytes::{ByteBuf, Bytes}; + +fn _bytes_eq_slice(bytes: &Bytes, slice: &[u8]) -> bool { + bytes == slice +} + +fn _bytebuf_eq_vec(bytebuf: ByteBuf, vec: Vec<u8>) -> bool { + bytebuf == vec +} + +fn _bytes_eq_bytestring(bytes: &Bytes) -> bool { + bytes == b"..." +} diff --git a/tests/test_serde.rs b/tests/test_serde.rs new file mode 100644 index 0000000..d48e3fd --- /dev/null +++ b/tests/test_serde.rs @@ -0,0 +1,59 @@ +use serde_bytes::{ByteBuf, Bytes}; +use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token}; + +#[test] +fn test_bytes() { + let empty = Bytes::new(&[]); + assert_tokens(&empty, &[Token::BorrowedBytes(b"")]); + assert_ser_tokens(&empty, &[Token::Bytes(b"")]); + assert_ser_tokens(&empty, &[Token::ByteBuf(b"")]); + assert_de_tokens(&empty, &[Token::BorrowedStr("")]); + + let buf = vec![65, 66, 67]; + let bytes = Bytes::new(&buf); + assert_tokens(&bytes, &[Token::BorrowedBytes(b"ABC")]); + assert_ser_tokens(&bytes, &[Token::Bytes(b"ABC")]); + assert_ser_tokens(&bytes, &[Token::ByteBuf(b"ABC")]); + assert_de_tokens(&bytes, &[Token::BorrowedStr("ABC")]); +} + +#[test] +fn test_byte_buf() { + let empty = ByteBuf::new(); + assert_tokens(&empty, &[Token::BorrowedBytes(b"")]); + assert_tokens(&empty, &[Token::Bytes(b"")]); + assert_tokens(&empty, &[Token::ByteBuf(b"")]); + assert_de_tokens(&empty, &[Token::BorrowedStr("")]); + assert_de_tokens(&empty, &[Token::Str("")]); + assert_de_tokens(&empty, &[Token::String("")]); + assert_de_tokens(&empty, &[Token::Seq { len: None }, Token::SeqEnd]); + assert_de_tokens(&empty, &[Token::Seq { len: Some(0) }, Token::SeqEnd]); + + let buf = ByteBuf::from(vec![65, 66, 67]); + assert_tokens(&buf, &[Token::BorrowedBytes(b"ABC")]); + assert_tokens(&buf, &[Token::Bytes(b"ABC")]); + assert_tokens(&buf, &[Token::ByteBuf(b"ABC")]); + assert_de_tokens(&buf, &[Token::BorrowedStr("ABC")]); + assert_de_tokens(&buf, &[Token::Str("ABC")]); + assert_de_tokens(&buf, &[Token::String("ABC")]); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: None }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: Some(3) }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); +} |