diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..16859a7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [FlixCoder] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..61e8f94 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,50 @@ +name: Rust + +on: + push: + branches: ["main"] + tags: ["*"] + pull_request: + branches: ["*"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + formatting: + name: Formatting + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install/update Rust + shell: bash + run: rustup toolchain install nightly --component rustfmt + + - name: Formatting + shell: bash + run: cargo +nightly fmt -- --check + + checks: + name: Tests and Lints + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install/update Rust + shell: bash + run: rustup show # Uses rust-toolchain.toml file to install the correct version and components. + + - uses: taiki-e/install-action@v2 + with: + tool: cargo-make + + - name: Caching + uses: Swatinem/rust-cache@v2 + + - name: Run the CI checks + shell: bash + run: cargo make stable-ci diff --git a/.gitignore b/.gitignore index ea8c4bf..81cf465 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9380b58 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.1.0] - 2024-09-29 + +### Features + +- Initial implementation diff --git a/Cargo.lock b/Cargo.lock index 531c920..fcb142a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,100 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "brief" version = "0.1.0" dependencies = [ + "heapless", "serde", + "serde_bytes", + "serde_json", "tracing", + "tracing-subscriber", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", ] [[package]] @@ -16,6 +104,12 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -40,6 +134,56 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "serde" version = "1.0.210" @@ -49,6 +193,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -60,6 +213,39 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" version = "2.0.77" @@ -71,6 +257,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tracing" version = "0.1.40" @@ -100,6 +296,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -107,3 +333,31 @@ name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 0596611..58378a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,46 @@ [package] authors = ["Flix "] -categories = [] +categories = ["encoding", "parser-implementations", "no-std", "embedded"] description = "A brief, self-descriptive, serde-compatible binary format." documentation = "https://docs.rs/brief" edition = "2021" +exclude = ["/tests/data", "/.github"] homepage = "https://github.com/FlixCoder/fhir-sdk" -keywords = ["binary", "serde", "encoding", "data"] +keywords = ["serde", "encoding", "binary", "data", "no-std", "no-std::no-alloc"] license = "MIT" name = "brief" readme = "README.md" repository = "https://github.com/FlixCoder/brief" -rust-version = "1.75" +rust-version = "1.81" version = "0.1.0" [features] default = [] +alloc = ["serde/alloc"] +std = ["alloc", "serde/std", "tracing?/std"] tracing = ["dep:tracing"] +heapless = ["dep:heapless"] [dependencies] -serde = "1.0.210" -tracing = { version = "0.1.40", optional = true } +heapless = { version = "0.8.0", optional = true, features = ["serde"] } +serde = { version = "1.0.210", default-features = false } +tracing = { version = "0.1.40", optional = true, default-features = false, features = ["attributes"] } +[dev-dependencies] +serde = { version = "1.0.210", features = ["derive"] } +serde_bytes = "0.11.15" +serde_json = "1.0.128" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } + +# Also test the examples +[[example]] +name = "simple" +path = "examples/simple.rs" +test = true + + +# Add more lints. [lints.rust] dead_code = "warn" missing_debug_implementations = "warn" @@ -31,7 +50,8 @@ trivial_numeric_casts = "warn" unused_extern_crates = "warn" [lints.clippy] -tabs_in_doc_comments = "allow" +tabs_in_doc_comments = "allow" # Rustfmt setting. +unnecessary_lazy_evaluations = "allow" # Performance better, because `Drop`. allow_attributes_without_reason = "warn" branches_sharing_code = "warn" cast_lossless = "warn" @@ -109,3 +129,8 @@ unwrap_used = "warn" used_underscore_binding = "warn" useless_let_if_seq = "warn" verbose_file_reads = "warn" + + +# Metadata for docs.rs to enable features and show everything. +[package.metadata.docs.rs] +all-features = true diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..0f6b903 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,133 @@ +[config] +skip_core_tasks = true +default_to_workspace = false +time_summary = true + + +[env] +CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true +RUST_BACKTRACE = 1 + + +[tasks.install-rust-nightly-rustfmt] +private = true +description = "Installs nightly Rust with rustfmt (hopefully)." +toolchain = "nightly" +install_crate = { rustup_component_name = "rustfmt", binary = "rustfmt", test_arg = "--version" } + +[tasks.install-rust-toolchain] +private = true +description = "Installs the used Rust toolchain with specified components." +command = "rustup" +args = ["show"] + + +[tasks.format] +description = "Formats all Rust code." +install_crate = false +command = "cargo" +args = ["+nightly", "fmt"] +dependencies = ["install-rust-nightly-rustfmt"] + +[tasks.formatting] +description = "Checks all Rust code formatting." +install_crate = false +command = "cargo" +args = ["+nightly", "fmt", "--", "--check"] +dependencies = ["install-rust-nightly-rustfmt"] + + +[tasks.clippy-default] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy-none] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--no-default-features", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy-alloc] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--no-default-features", "--features", "alloc", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy-std] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--no-default-features", "--features", "std", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy-heapless] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--no-default-features", "--features", "heapless", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy-all] +install_crate = false +command = "cargo" +args = ["clippy", "--workspace", "--all-targets", "--all-features", "--", "-D", "warnings"] +dependencies = ["install-rust-toolchain"] + +[tasks.clippy] +description = "Runs clippy with all various feature sets." +dependencies = [ + "clippy-default", + "clippy-none", + "clippy-alloc", + "clippy-std", + "clippy-heapless", + "clippy-all", +] + + +[tasks.test-all-features] +description = "Runs all tests via cargo test with all features." +install_crate = false +command = "cargo" +args = ["test", "--workspace", "--all-features"] +dependencies = ["install-rust-toolchain"] + +[tasks.test-nextest] +description = "Runs all tests via nextest (installs nextest if necessary)." +install_crate = true +command = "cargo" +args = ["nextest", "run", "--workspace", "--all-features"] +dependencies = ["install-rust-toolchain"] + +[tasks.test-docs] +description = "Runs all doc-tests (since nextest does not)." +install_crate = false +command = "cargo" +args = ["test", "--workspace", "--all-features", "--doc"] +dependencies = ["install-rust-toolchain"] + +[tasks.nextest] +description = "Runs all tests via cargo nextest." +dependencies = ["test-nextest", "test-docs"] + +[tasks.test] +description = "Runs all tests via cargo test." +dependencies = ["test-all-features"] # Future: potentially some no-std test. + + +[tasks.stable-ci] +description = """ +Runs all CI checks with stable Rust (all but formatting). +""" +dependencies = ["test", "clippy"] + +[tasks.ci] +description = """ +Runs all checks necessary for CI to pass. +This includes formatting, clippy and tests currently. +""" +dependencies = ["test", "clippy", "formatting"] + + +[tasks.default] +alias = "ci" diff --git a/README.md b/README.md index 425f465..3e6e523 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,118 @@ # Brief -A brief, self-descriptive, serde-compatible binary format. +[![crates.io page](https://img.shields.io/crates/v/brief.svg)](https://crates.io/crates/brief) +[![docs.rs page](https://docs.rs/brief/badge.svg)](https://docs.rs/brief/) +![license: MIT](https://img.shields.io/crates/l/brief.svg) + +Brief (German for letter) is a crate for encoding and decoding data into a binary format that is self-descriptive and [serde](https://docs.rs/serde/)-compatible. + +## Design Goals + +Not necessarily in order of importance: + +- Convenient to use for developers: Integrates into the Rust ecosystem via `serde`, supporting all of its features in its derived implementations (e.g. renaming, flattening, ..). +- Compatibility: Easy to add or re-order fields/variants without breakage. +- `#![no_std]` and std compatible. +- Resource efficient: High performance, low memory usage. +- Interoperability: Different architectures can communicate flawlessly. +- Well-tested: Ensure safety (currently, there is no use of `unsafe`). + +## Binary Format + +The format is new and therefore NOT YET STABLE. + +The format is specified [here](./docs/format-specification.md). + +### Flavors / Modes + +By default, structs' field names and enums' variant names are encoded as strings. This can be configured to be encoded as unsigned integers of their indices instead. However, this has compatibility implications and some serde features do not work with the index representation. See the format specification for more info. + +## Comparisons + +How does Brief compare to ..? + +### [Postcard](https://docs.rs/postcard/) + +Postcard is NOT a self-describing format. It's encoding solely consists of the raw data and the deserializer needs to have the same information on the data schema. This makes it more difficult to change the data format, e.g. add new fields. + +Postcard is producing way smaller encoded data due to the missing schema information and field names. It is also faster. + +Brief supports decoding unknown data and parsing it into the requested structures regardless of additional fields or different orders. + +### [Pot](https://docs.rs/pot/) + +Pot is a self-describing format as well. It's encoding is more space-efficient due to reducing repeated type/schema definitions. This comes at the cost of serialization/deserialization speed. + +It is also not no-std compatible. + +Brief is faster most of the times, but less space-efficient. + +### [Serde_json](https://docs.rs/serde_json/) + +JSON is a self-describing format as well. However, it is text based and therefore requires string escaping. Bytes cannot be efficiently represented. However, JSON is widely adopted, as you already know :D + +In Brief, map keys can not only be strings. Unlike in JSON, keys can be nested data, so something like `HashMap` can be serialized and deserialized without issues. + +Brief is both more space-efficient and faster. + +## Usage + +Add the library to your project with `cargo add brief`. By default, no features are enabled (currently), so it is no-std by default. You can enable use of `Vec`s and such with features like `alloc` or `std`. + +### Example Serialization/Deserialization + +The `heapless` feature was enabled for this example. It is similarly possible with `std`'s `Vec` or just slices. + +```rust +use heapless::Vec; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +struct MyBorrowedData<'a> { + name: &'a str, + age: u8, +} + +let data = MyBorrowedData { name: "Holla", age: 21 }; +let mut output: Vec = brief::to_heapless_vec(&data).unwrap(); + +assert_eq!(output, [ + 17, + 11, 4, b'n', b'a', b'm', b'e', 11, 5, b'H', b'o', b'l', b'l', b'a', + 11, 3, b'a', b'g', b'e', 3, 21, + 18 +]); + +let parsed: MyBorrowedData = brief::from_slice(&output).unwrap(); +assert_eq!(parsed, data); +``` + +## Benchmarks + +For now, see [here](https://github.com/FlixCoder/rust_serialization_benchmark/tree/add-brief). + +### Results + +The serialization/deserialization is reasonably fast. Between postcard and serde_json mostly. The data-size is also between postcard and JSON. + +I expect there is a lot improvements possible, we are still way slower than postcard sadly. + +## Development & Testing + +1. Install [cargo-make](https://github.com/sagiegurari/cargo-make) (and optionally [cargo-nextest](https://github.com/nextest-rs/nextest)): `cargo install cargo-make cargo-nextest`. +2. Optional, but recommended: Put `search_project_root = true` into cargo-make's user configuration, so that `cargo make` can be run from sub-folders. +3. From the project directory, you can run the following tasks: + - **Format code**: `cargo make format` + - **Check formatting**: `cargo make formatting` + - **Run all tests via cargo test**: `cargo make test` + - **Run all tests via cargo nextest**: `cargo make nextest` + - **Run clippy for all feature sets, failing on any warnings**: `cargo make clippy` + - **Do all checks that are done in CI**: `cargo make ci` + +## Minimum supported Rust version + +Currently, I am always using the latest stable Rust version and do not put in effort to keep the MSRV. Please open an issue in case you need a different policy, I might consider changing the policy. + +## License + +Licensed under the MIT license. All contributors agree to license under this license. diff --git a/docs/format-specification.md b/docs/format-specification.md new file mode 100644 index 0000000..f7a6a99 --- /dev/null +++ b/docs/format-specification.md @@ -0,0 +1,166 @@ +# Brief Binary Format + +The format is close to JSON, modified to be better, binary and fit to [serde's data model](https://serde.rs/data-model.html). + +## Stability + +The format is not considered stable as of yet. + +## Self-Describing Format + +The format includes information on the structure of the data. + +**Advantages** over non-self-describing formats: + +- There is no need for a schema to parse any given data. +- Easy to provide backwards/forwards compatibility of data formats, as it is possible to add new fields. +- Type compatibility can be checked. + +**Disadvantages** over non-self-describing formats: + +- Larger binary representation. +- Additional parsing / overhead. + +## Defined Types + +Every value in Brief is prepended with a byte detailing its type. +The Brief format currently contains these types: + +| Type | Description | Byte value | +| --- | --- | --- | +| Null | No value. | 0 | +| BooleanFalse | Boolean with value `false`. | 1 | +| BooleanTrue | Boolean with value `true`. | 2 | +| UnsignedInt | Unsigned integer. The following bytes are the value in "VarInt" encoding (see below). | 3 | +| SignedInt | Signed integer. The following bytes are the value in "VarInt" encoding (see below). | 4 | +| Float16 | Float with 16-bit precision (not yet used/supported). | 5 | +| Float32 | Float with 32-bit precision. The next 4 bytes are the value (little-endian). | 6 | +| Float64 | Float with 64-bit precision. The next 8 bytes are the value (little-endian). | 7 | +| Float128 | Float with 128-bit precision (not yet used/supported). | 8 | +| Bytes | Raw bytes. The following bytes are the length of the byte sequence (must fit into `usize`). After that come the raw bytes of the given length. | 10 | +| String | UTF-8 string. The following bytes are the length of the byte sequence (must fit into `usize`). After that come the string's raw bytes of the given length. | 11 | +| SeqStart | A sequence of any number of values of any type. There is no specified length. The following bytes are the sequence's values. The end of the sequence is recognized by the SeqEnd type. | 15 | +| SeqEnd | The end of a sequence. | 16 | +| MapStart | A map of any number of key-value pairs of any types. There is no specified length. The following bytes are the map's keys and values. The end of the sequence is recognized by the SeqEnd type. | 17 | +| MapEnd | The end of a map. | 18 | + +### Examples + +- `[0]`: null value +- `[1]`: `false` +- `[2]`: `true` +- `[3, 0]`: `0` +- `[4, 1]`: `-1` +- `[10, 0]`: byte sequence of length 0 +- `[10, 1, 5]`: byte sequence of length 1 containing a byte with value `5` +- `[15, 16]`: empty sequence +- `[15, 0, 1, 16]`: sequence with 2 values: `null` and `false` +- `[17, 18]`: empty map +- `[17, 3, 0, 2, 18]`: map with 1 key-value pair: `0 -> true` + +## VarInt Encoding + +All integers are encoded in this format. It allows to use the same format for all integer numbers, regardless of size. It also saves space for small integers. The format is identical to [postcard's VarInt encoding](https://postcard.jamesmunns.com/wire-format#varint-encoded-integers). Also see [Wikipedia's article on VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity). + +For every byte, the most significant bit determines whether this is the last byte of the number. For example, `0x83`/`0b1000_0011` will result in another byte being read for the current number. `0x73`/`0b0111_0011` will be considered the last byte. +Every byte's lower 7 bits are used to store the actual value. + +Unsigned integers are encoded least-significant-bits first. For example, `0x017F`/`0b0000_0001_0111_1111` will be encoded like this: `0xFF`/`0b1111_1111`, `0x02`/`0b0000_0010`. +*Further explanation*: The least significant 7 bits are `111_1111`. Since we need another byte to store the number's rest of the bits, the 8th bit will be `1`, too. Therefore, out first bit is `0xFF`. The next 7 bytes of our number are `000_0010`. We don't need any more bytes after this one, as the value needs less than 14 bits, therefore the 8th bit is `0`. The encoded byte is `0x02`. + +Signed integers would blow up this encoding, since `-1` is `0xFFFF_FFFF_FFFF_FFFF` in two's-complement in a `u64`. Therefore, signed integers are [ZigZag](https://en.wikipedia.org/wiki/Variable-length_quantity)-encoded first. The sign ends up in the lowest bit in the first byte. `-1` would be encoded as `0b0000_0001`. `1` is encoded as `0b0000_0010`. + +### Maximum Length + +There is no length limit on the number's encoding in the format itself. In practice however, `serde` supports up to 128 bits and the deserialization will fail on any numbers larger than the expected type. So reading a `u8` will fail when there is more than 2 bytes or more than 8 value-bits. A 128 bit value will never exceed 19 bytes. +Other parsers would, in theory, be allowed to encode arbitrarily large numbers in any amount of bytes. + +### Canonicalization + +The encoding allows values to pad numbers with any number of 0s, e.g. a chain of `0x80` bytes. The number `0` could be represented as `0x80`, `0x80`, `0x80`, `0x00`. Four bytes, despite being value `0`. The serializer will always output numbers with the lowest number of bytes. However, the deserializer will accept representations with additional padding up to the maximum number of bits of the expected type. + +### Architecture-Specific Sizes + +The `isize` and `usize` types are as wide as pointers on the specific system. This means, the maximum/minimum number can differ across systems. The VarInt encoding works the same way, so different systems can communicate without any issues, as long as the value fits into the *smallest* of the system's architecture. Parsing will fail on the smaller architecture otherwise. + +## Sequences and Maps + +Sequences/arrays and maps do not specify their length, so any number of values can follow. Their end is denoted by a value of a special end type. + +Values can have any type, so even maps can consist of arbitrarily complex keys and values. The key itself could be a structure 2 layers deep. The type of every value can differ. + +## Mapping of Rust Types to Encoded Data + +The encoding/serialization and decoding/deserialization happens via `serde`, so it follows the [serde data model](https://serde.rs/data-model.html). Please familiarize yourself with its concept to fully understand the following. In any case, the following describes how Rust types are mapped to Brief format types. + +There are two modes of the format. The first and default encodes structs as maps with keys being strings of the fields' names. The second encodes structs as maps with keys being unsigned integers, where the value denotes the index/position in the struct. Similarly, the same happens for enums. Variants are encoded either as string or as unsigned integer denoting their index (NOT discriminant). + +Note that (at least currently) the deserializer can parse data regardless of which encoding was used, unless it relies on features that do not work with index representation mode (e.g. internally tagged enums). The serializer however needs to know which format it needs to serialize to. + +**Advantages of the default (string representation)**: + +- Compatibility and robustness: adding or re-ordering fields works without issues. +- Support of `#[serde(rename)]`, internally tagged enums and any other serde feature. The index representation does NOT support renaming fields. It also cannot deserialize internally tagged enums. This is due to the way `serde` handles internally tagged enums. Externally or adjacently tagged enums DO work, as well as untagged enums. Please note however, that untagged enum variants can more easily be differentiated with named fields. +- External parties can understand the data more easily with named fields. + +**Advantages of the index representation**: + +- Smaller footprint: strings need more space in the encoding. + +### Serde Datatypes in Brief (String Representation) + +The list of serde's types can be found [here](https://serde.rs/data-model.html), along with how Rust types are mapped to serde's types. + +| Serde Type | Brief Type | Description | +| --- | --- | --- | +| bool | BooleanFalse or BooleanTrue | Value is saved within the type. No additional value. | +| u8, u16, u32, u64, u128 | UnsignedInt | VarInt encoded. | +| i8, i16, i32, i64, i128 | SignedInt | ZigZag encoded and then VarInt encoded. | +| f32 | Float32 | 4 bytes containing the raw value (little-endian). | +| f64 | Float64 | 8 bytes containing the raw value (little-endian). | +| char | String | UTF-8 encoded and serialized as string. | +| string | String | First, the length (bytes, not chars) in VarInt encoding is given (unsigned). Then the raw bytes follow. The bytes must be a UTF-8 encoded string. | +| byte array | Bytes | First, the length in VarInt encoding is given (unsigned). Then the raw bytes follow. | +| sequence | SeqStart .. SeqEnd | SeqStart is the type for starting a sequence. Any number of values follow. A SeqEnd at the correct position will end the sequence. | +| map | MapStart .. MapEnd | MapStart is the type for starting a map. Any number of key-value pairs follow. The keys and values are not separated, they are differentiated by position. A MapEnd at the correct position will end the map. | +| option | Null or any other type. | `None` becomes the `Null` type. Any other value is directly encoded as its type. Note that `Option<()>` will always be `Null` and decoded as `None`. | +| tuple | SeqStart .. SeqEnd | Encoded as sequence. Information that the length is fixed is unused and not saved. | +| unit | Null | Always `Null`. | +| unit struct | Null | Struct names are not used. There is no value, similar to the unit type. | +| newtype struct | Any | Structs names are not used. Newtype structs (only one field) are encoded as their inner value (transparent encoding). | +| tuple struct | SeqStart .. SeqEnd | Struct names are not used. Therefore encoded just as a tuple (so as a sequence). | +| struct | MapStart .. MapEnd | Struct names are not used. Encoded as a map with keys being the field names and values being their encoded values. | +| unit variant | String | Enum names are not used. Variants without data are just the variant name as string. | +| newtype variant | MapStart, String, Any, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant name as string. The value is the encoded value. | +| tuple variant | MapStart, String, SeqStart .. SeqEnd, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant name as string. The value is a sequence of the encoded values. | +| struct variant | MapStart, String, MapStart .. MapEnd, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant name as string. The value is a map of the field names to their values. | + +### Serde Datatypes in Brief (Index Representation) + +The list of serde's types can be found [here](https://serde.rs/data-model.html), along with how Rust types are mapped to serde's types. + +The index representation does not work with internally tagged enums (`#[serde(tag = "t")]`). Externally or adjacently tagged enums do work (nothing or `#[serde(tag = "type", content = "c")]`). + +| Serde Type | Brief Type | Description | +| --- | --- | --- | +| bool | BooleanFalse or BooleanTrue | Value is saved within the type. No additional value. | +| u8, u16, u32, u64, u128 | UnsignedInt | VarInt encoded. | +| i8, i16, i32, i64, i128 | SignedInt | ZigZag encoded and then VarInt encoded. | +| f32 | Float32 | 4 bytes containing the raw value (little-endian). | +| f64 | Float64 | 8 bytes containing the raw value (little-endian). | +| char | String | UTF-8 encoded and serialized as string. | +| string | String | First, the length (bytes, not chars) in VarInt encoding is given (unsigned). Then the raw bytes follow. The bytes must be a UTF-8 encoded string. | +| byte array | Bytes | First, the length in VarInt encoding is given (unsigned). Then the raw bytes follow. | +| sequence | SeqStart .. SeqEnd | SeqStart is the type for starting a sequence. Any number of values follow. A SeqEnd at the correct position will end the sequence. | +| map | MapStart .. MapEnd | MapStart is the type for starting a map. Any number of key-value pairs follow. The keys and values are not separated, they are differentiated by position. A MapEnd at the correct position will end the map. | +| option | Null or any other type. | `None` becomes the `Null` type. Any other value is directly encoded as its type. Note that `Option<()>` will always be `Null` and decoded as `None`. | +| tuple | SeqStart .. SeqEnd | Encoded as sequence. Information that the length is fixed is unused and not saved. | +| unit | Null | Always `Null`. | +| unit struct | Null | Struct names are not used. There is no value, similar to the unit type. | +| newtype struct | Any | Structs names are not used. Newtype structs (only one field) are encoded as their inner value (transparent encoding). | +| tuple struct | SeqStart .. SeqEnd | Struct names are not used. Therefore encoded just as a tuple (so as a sequence). | +| struct | MapStart .. MapEnd | Struct names are not used. Encoded as a map with keys being the field indices (`u32`) and values being their encoded values. | +| unit variant | String | Enum names are not used. Variants without data are just the variant index as unsigned integer (`u32`). | +| newtype variant | MapStart, String, Any, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant index as unsigned integer (`u32`). The value is the encoded value. | +| tuple variant | MapStart, String, SeqStart .. SeqEnd, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant index as unsigned integer (`u32`). The value is a sequence of the encoded values. | +| struct variant | MapStart, String, MapStart .. MapEnd, MapEnd | Enum names are not used. Variants with values are a map with a single key-value pair. The key is the variant index as unsigned integer (`u32`). The value is a map of the field indices (`u32`) to their values. | diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..ad46c79 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,32 @@ +//! Simple serialization/deserialization example. +#![allow(clippy::missing_docs_in_private_items, clippy::unwrap_used, reason = "Example")] + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +struct MyBorrowedData<'a> { + name: &'a str, + age: u8, +} + +fn main() { + let data = MyBorrowedData { name: "Holla", age: 21 }; + let mut output = [0; 22]; + let bytes = brief::to_slice(&data, &mut output).unwrap(); + + assert_eq!( + bytes, + [ + 17, 11, 4, b'n', b'a', b'm', b'e', 11, 5, b'H', b'o', b'l', b'l', b'a', 11, 3, b'a', + b'g', b'e', 3, 21, 18 + ] + ); + + let parsed: MyBorrowedData = brief::from_slice(bytes).unwrap(); + assert_eq!(parsed, data); +} + +#[test] +fn run() { + main(); +} diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..a844884 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,207 @@ +//! Scratch/buffer implementation for different environments. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +use crate::{Error, Result}; + +/// Scratch/buffer implementation for different environments. +pub trait Buffer { + /// Clear the buffer. + fn clear(&mut self); + /// Get the written buffer value as a slice. + fn as_slice(&self) -> &[u8]; + /// Push a byte to the buffer. + fn push(&mut self, byte: u8) -> Result<()>; + /// Extend the buffer with the given slice. + fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()>; + /// Reserve space in the buffer and return a mutable slice to it to be written. + fn reserve_slice(&mut self, len: usize) -> Result<&mut [u8]>; +} + +/// Mostly as a type when no buffer is given, not a real buffer. +impl Buffer for () { + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn clear(&mut self) {} + + fn as_slice(&self) -> &[u8] { + &[] + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn push(&mut self, _byte: u8) -> Result<()> { + Err(Error::BufferTooSmall) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, bytes)))] + fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()> { + if bytes.is_empty() { + Ok(()) + } else { + Err(Error::BufferTooSmall) + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn reserve_slice(&mut self, len: usize) -> Result<&mut [u8]> { + if len == 0 { + Ok(&mut []) + } else { + Err(Error::BufferTooSmall) + } + } +} + +#[cfg(feature = "alloc")] +impl Buffer for ::alloc::vec::Vec { + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn clear(&mut self) { + self.clear(); + } + + fn as_slice(&self) -> &[u8] { + self.as_slice() + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn push(&mut self, byte: u8) -> Result<()> { + self.try_reserve(1).map_err(|_| Error::Allocation)?; + self.push(byte); + Ok(()) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, bytes)))] + fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()> { + self.try_reserve(bytes.len()).map_err(|_| Error::Allocation)?; + self.extend_from_slice(bytes); + Ok(()) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn reserve_slice(&mut self, len: usize) -> Result<&mut [u8]> { + self.try_reserve(len).map_err(|_| Error::Allocation)?; + let prev = self.len(); + self.resize(prev.checked_add(len).ok_or_else(|| Error::UsizeOverflow)?, 0); + Ok(self.as_mut_slice().split_at_mut(prev).1) + } +} + +#[cfg(feature = "heapless")] +impl Buffer for ::heapless::Vec { + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn clear(&mut self) { + self.clear(); + } + + fn as_slice(&self) -> &[u8] { + self.as_slice() + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn push(&mut self, byte: u8) -> Result<()> { + self.push(byte).map_err(|_| Error::BufferTooSmall) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, bytes)))] + fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()> { + self.extend_from_slice(bytes).map_err(|_| Error::BufferTooSmall) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn reserve_slice(&mut self, len: usize) -> Result<&mut [u8]> { + let prev = self.len(); + self.resize(prev.checked_add(len).ok_or_else(|| Error::UsizeOverflow)?, 0) + .map_err(|_| Error::BufferTooSmall)?; + Ok(self.as_mut_slice().split_at_mut(prev).1) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, reason = "Tests")] + + use super::*; + + fn does_not_panic(mut buffer: B) { + buffer.clear(); + _ = buffer.as_slice(); + _ = buffer.push(0); + _ = buffer.extend_from_slice(&[]); + _ = buffer.extend_from_slice(&[5]); + _ = buffer.extend_from_slice(&[1, 2, 3, 4, 5]); + _ = buffer.reserve_slice(0); + _ = buffer.reserve_slice(1); + _ = buffer.reserve_slice(usize::MAX / 2); + _ = buffer.reserve_slice(usize::MAX); + } + + #[allow(dead_code, reason = "Different feature sets")] + fn basics_work(mut buffer: B) { + buffer.clear(); + let expected: &[u8] = &[]; + assert_eq!(buffer.as_slice(), expected); + buffer.push(1).unwrap(); + assert_eq!(buffer.as_slice(), &[1]); + buffer.push(2).unwrap(); + assert_eq!(buffer.as_slice(), &[1, 2]); + buffer.extend_from_slice(&[3, 4]).unwrap(); + assert_eq!(buffer.as_slice(), &[1, 2, 3, 4]); + buffer.push(5).unwrap(); + assert_eq!(buffer.as_slice(), &[1, 2, 3, 4, 5]); + buffer.clear(); + let expected: &[u8] = &[]; + assert_eq!(buffer.as_slice(), expected); + buffer.extend_from_slice(&[]).unwrap(); + let expected: &[u8] = &[]; + assert_eq!(buffer.as_slice(), expected); + buffer.extend_from_slice(&[1]).unwrap(); + assert_eq!(buffer.as_slice(), &[1]); + buffer.extend_from_slice(&[2, 3, 4, 5]).unwrap(); + assert_eq!(buffer.as_slice(), &[1, 2, 3, 4, 5]); + } + + #[allow(dead_code, reason = "Different feature sets")] + fn reserve_slice_works(mut buffer: B) { + buffer.clear(); + let slice = buffer.reserve_slice(0).unwrap(); + let expected: &mut [u8] = &mut []; + assert_eq!(slice, expected); + let slice = buffer.reserve_slice(10).unwrap(); + assert_eq!(slice.len(), 10); + assert_eq!(slice, &mut [0; 10]); + for (i, target) in slice.iter_mut().enumerate() { + *target = i as u8; + } + assert_eq!(slice, &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let slice = buffer.as_slice(); + assert_eq!(slice, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + let slice = buffer.reserve_slice(1).unwrap(); + slice[0] = 10; + assert_eq!(slice, &[10]); + let slice = buffer.as_slice(); + assert_eq!(slice, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + } + + #[test] + fn unit_buffer_behaves() { + does_not_panic(()); + } + + #[cfg(feature = "alloc")] + #[test] + fn vec_buffer_behaves() { + does_not_panic(::alloc::vec::Vec::new()); + basics_work(::alloc::vec::Vec::new()); + reserve_slice_works(::alloc::vec::Vec::new()); + } + + #[cfg(feature = "heapless")] + #[test] + fn heapless_buffer_behaves() { + does_not_panic(::heapless::Vec::<_, 100>::new()); + basics_work(::heapless::Vec::<_, 100>::new()); + reserve_slice_works(::heapless::Vec::<_, 100>::new()); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..aa237d4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,23 @@ +//! Configuration for (de-)serialization. + +use ::core::num::NonZeroUsize; + +// TODO: +// - Add max sequence/map size limit. +// - Add max-depth limit. +/// Configuration for (de-)serialization. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct Config { + /// Whether to use indices instead of strings as keys for struct-fields/enum-variants. + pub use_indices: bool, + /// Whether to return an error if there is excess data in the input. + pub error_on_excess_data: bool, + /// Maximum number of bytes to read or write, in any limit. + pub max_size: Option, +} + +impl Default for Config { + fn default() -> Self { + Self { use_indices: false, error_on_excess_data: true, max_size: None } + } +} diff --git a/src/de.rs b/src/de.rs index 909cb95..911ae2e 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1 +1,1213 @@ //! Deserialization implementation. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +use ::core::str; +use ::serde::de::{IntoDeserializer, Unexpected, Visitor}; + +use crate::{ + buffer::Buffer, + format::{Type, VarInt}, + io::Input, + Error, Result, +}; + +/// The deserializer for the binary format. +#[derive(Debug)] +pub struct Deserializer { + /// The input to read from. + input: I, + /// The buffer/scratch to read data to temporarily. + buffer: Option, +} + +impl Deserializer { + /// Create a new deserializer from the given input, without a scratch/buffer. When reading from + /// a non-borrowed source (e.g. a reader), set a read-buffer with + /// [with_buffer](Self::with_buffer) or deserialization will fail. + #[expect(clippy::missing_const_for_fn, reason = "Probably not const in the future")] + #[must_use] + pub fn new<'de>(input: I) -> Self + where + // Same bounds as `serde::Deserializer` impl. + I: Input<'de>, + { + Self { input, buffer: None } + } + + /// Create a new deserializer from the given input, without a scratch/buffer. Reading from a + /// non-borrowed source will fail (e.g. a reader). + #[must_use] + pub fn with_buffer(self, buffer: B) -> Deserializer + where + // Same bounds as `serde::Deserializer` impl. + B: Buffer, + { + Deserializer { input: self.input, buffer: Some(buffer) } + } +} + +impl Deserializer { + /// Consume the deserializer and return the input. + #[inline] + pub fn into_input(self) -> I { + self.input + } + + /// Consume the deserializer and return the inner parts. + #[inline] + pub fn into_parts(self) -> (I, Option) { + (self.input, self.buffer) + } +} + +impl<'de, I, B> Deserializer +where + I: Input<'de>, + B: Buffer, +{ + /// Reset the buffer, if available. + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn reset_buffer(&mut self) { + if let Some(buffer) = self.buffer.as_mut() { + buffer.clear(); + } + } + + /// Get the buffer as slice. + #[inline] + fn buffer_slice(&self) -> Result<&[u8]> { + Ok(self.buffer.as_ref().ok_or_else(|| Error::BufferTooSmall)?.as_slice()) + } + + /// Read a number of bytes, regardless of lifetime. + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn read_bytes<'s>(&'s mut self, len: usize) -> Result<&'s [u8]> + where + 'de: 's, + { + self.reset_buffer(); + if let Some(data) = self.input.read_bytes(len, self.buffer.as_mut())? { + Ok(data) + } else { + self.buffer_slice() + } + } + + /// Deserialize a usize/isize and visit it, regardless of size. + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_ptr(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let number = usize::decode(&mut self.input)?; + match size_of::() { + 1 => visitor.visit_u8(number as u8), + 2 => visitor.visit_u16(number as u16), + 4 => visitor.visit_u32(number as u32), + 8 => visitor.visit_u64(number as u64), + 16 => visitor.visit_u128(number as u128), + _ => unreachable!("usize must have one of these sizes"), + } + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let number = isize::decode(&mut self.input)?; + match size_of::() { + 1 => visitor.visit_i8(number as i8), + 2 => visitor.visit_i16(number as i16), + 4 => visitor.visit_i32(number as i32), + 8 => visitor.visit_i64(number as i64), + 16 => visitor.visit_i128(number as i128), + _ => unreachable!("isize must have one of these sizes"), + } + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt, Type::SignedInt])), + } + } + + /// Deserialize a float. + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_float(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + // Add Float16 once stable. + Type::Float32 => { + _ = self.input.read_byte()?; + let mut bytes = [0; 4]; + self.input.read_exact(&mut bytes)?; + let value = f32::from_le_bytes(bytes); + visitor.visit_f32(value) + } + Type::Float64 => { + _ = self.input.read_byte()?; + let mut bytes = [0; 8]; + self.input.read_exact(&mut bytes)?; + let value = f64::from_le_bytes(bytes); + visitor.visit_f64(value) + } + // Add Float128 once stable. + _ => Err(Error::WrongType( + t, + &[Type::Float16, Type::Float32, Type::Float64, Type::Float128], + )), + } + } + + /// Deserialize an unsigned integer. + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_unsigned_int(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u128::decode(&mut self.input)?; + if value <= u8::MAX as u128 { + visitor.visit_u8(value as u8) + } else if value <= u16::MAX as u128 { + visitor.visit_u16(value as u16) + } else if value <= u32::MAX as u128 { + visitor.visit_u32(value as u32) + } else if value <= u64::MAX as u128 { + visitor.visit_u64(value as u64) + } else { + visitor.visit_u128(value) + } + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + /// Deserialize a signed integer. + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_signed_int(&mut self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + #[allow(clippy::cast_lossless, reason = "We won't change it")] + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i128::decode(&mut self.input)?; + if (i8::MIN as i128 ..= i8::MAX as i128).contains(&value) { + visitor.visit_i8(value as i8) + } else if (i16::MIN as i128 ..= i16::MAX as i128).contains(&value) { + visitor.visit_i16(value as i16) + } else if (i32::MIN as i128 ..= i32::MAX as i128).contains(&value) { + visitor.visit_i32(value as i32) + } else if (i64::MIN as i128 ..= i64::MAX as i128).contains(&value) { + visitor.visit_i64(value as i64) + } else { + visitor.visit_i128(value) + } + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } +} + +impl<'a, 'de, I, B> ::serde::Deserializer<'de> for &'a mut Deserializer +where + I: Input<'de>, + B: Buffer, +{ + type Error = crate::Error; + + #[inline] + fn is_human_readable(&self) -> bool { + false + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => self.deserialize_unit(visitor), + Type::BooleanFalse | Type::BooleanTrue => self.deserialize_bool(visitor), + Type::UnsignedInt => self.deserialize_unsigned_int(visitor), + Type::SignedInt => self.deserialize_signed_int(visitor), + Type::Float16 | Type::Float32 | Type::Float64 | Type::Float128 => { + self.deserialize_float(visitor) + } + Type::Bytes => self.deserialize_byte_buf(visitor), + Type::String => self.deserialize_string(visitor), + Type::SeqStart => self.deserialize_seq(visitor), + Type::MapStart => self.deserialize_map(visitor), + Type::SeqEnd | Type::MapEnd => Err(Error::WrongType( + t, + &[ + Type::Null, + Type::BooleanFalse, + Type::BooleanTrue, + Type::UnsignedInt, + Type::SignedInt, + Type::Float16, + Type::Float32, + Type::Float64, + Type::Float128, + Type::Bytes, + Type::String, + Type::SeqStart, + Type::MapStart, + ], + )), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_unit() + } + _ => Err(Error::WrongType(t, &[Type::Null])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_unit() + } + _ => Err(Error::WrongType(t, &[Type::Null])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt | Type::SignedInt => self.deserialize_ptr(visitor), + _ => Err(Error::WrongType(t, &[Type::BooleanFalse, Type::BooleanTrue])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i8::decode(&mut self.input)?; + visitor.visit_i8(value) + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i16::decode(&mut self.input)?; + visitor.visit_i16(value) + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i32::decode(&mut self.input)?; + visitor.visit_i32(value) + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i64::decode(&mut self.input)?; + visitor.visit_i64(value) + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_i128(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SignedInt => { + _ = self.input.read_byte()?; + let value = i128::decode(&mut self.input)?; + visitor.visit_i128(value) + } + _ => Err(Error::WrongType(t, &[Type::SignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u8::decode(&mut self.input)?; + visitor.visit_u8(value) + } + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u16::decode(&mut self.input)?; + visitor.visit_u16(value) + } + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u32::decode(&mut self.input)?; + visitor.visit_u32(value) + } + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u64::decode(&mut self.input)?; + visitor.visit_u64(value) + } + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_u128(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let value = u128::decode(&mut self.input)?; + visitor.visit_u128(value) + } + Type::BooleanFalse => { + _ = self.input.read_byte()?; + visitor.visit_bool(false) + } + Type::BooleanTrue => { + _ = self.input.read_byte()?; + visitor.visit_bool(true) + } + _ => Err(Error::WrongType(t, &[Type::UnsignedInt])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_float(visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_float(visitor) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + let bytes = self.read_bytes(len)?; + let s = str::from_utf8(bytes)?; + + let mut chars = s.chars(); + let c = chars.next().ok_or_else(|| Error::NotOneChar)?; + if chars.next().is_some() { + return Err(Error::NotOneChar); + } + + visitor.visit_char(c) + } + _ => Err(Error::WrongType(t, &[Type::String])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + + self.reset_buffer(); + let borrowed = self.input.read_bytes(len, self.buffer.as_mut())?; + if let Some(borrowed) = borrowed { + let s = str::from_utf8(borrowed)?; + visitor.visit_borrowed_str(s) + } else { + let s = str::from_utf8(self.buffer_slice()?)?; + visitor.visit_str(s) + } + } + _ => Err(Error::WrongType(t, &[Type::String])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::UnsignedInt => self.deserialize_u32(visitor), + Type::String => self.deserialize_str(visitor), + _ => Err(Error::WrongType(t, &[Type::UnsignedInt, Type::String])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::Bytes | Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + + self.reset_buffer(); + let borrowed = self.input.read_bytes(len, self.buffer.as_mut())?; + if let Some(borrowed) = borrowed { + visitor.visit_borrowed_bytes(borrowed) + } else { + visitor.visit_bytes(self.buffer_slice()?) + } + } + _ => Err(Error::WrongType(t, &[Type::Bytes])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + _ => visitor.visit_some(self), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::SeqStart => { + _ = self.input.read_byte()?; + let value = visitor.visit_seq(SequenceDeserializer(self))?; + + let byte = self.input.read_byte()?; + let t = Type::try_from(byte)?; + if t == Type::SeqEnd { + Ok(value) + } else { + Err(Error::WrongType(t, &[Type::SeqEnd])) + } + } + Type::Bytes => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + let bytes = self.read_bytes(len)?; + let value = visitor.visit_seq(ByteSequenceDeserializer(bytes))?; + Ok(value) + } + Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + let bytes = self.read_bytes(len)?; + let s = str::from_utf8(bytes)?; + let value = visitor.visit_seq(CharSequenceDeserializer(s.chars()))?; + Ok(value) + } + _ => Err(Error::WrongType(t, &[Type::SeqStart])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::MapStart => { + _ = self.input.read_byte()?; + let value = visitor.visit_map(MapDeserializer(self))?; + + let byte = self.input.read_byte()?; + let t = Type::try_from(byte)?; + if t == Type::MapEnd { + Ok(value) + } else { + Err(Error::WrongType(t, &[Type::MapEnd])) + } + } + _ => Err(Error::WrongType(t, &[Type::MapStart])), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null => { + _ = self.input.read_byte()?; + visitor.visit_none() + } + Type::UnsignedInt => { + _ = self.input.read_byte()?; + let index = u32::decode(&mut self.input)?; + visitor.visit_enum(index.into_deserializer()) + } + Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + let bytes = self.read_bytes(len)?; + let s = str::from_utf8(bytes)?; + visitor.visit_enum(s.into_deserializer()) + } + Type::MapStart => { + _ = self.input.read_byte()?; + let value = visitor.visit_enum(EnumMapDeserializer(self))?; + + let byte = self.input.read_byte()?; + let t = Type::try_from(byte)?; + if t == Type::MapEnd { + Ok(value) + } else { + Err(Error::WrongType(t, &[Type::MapEnd])) + } + } + _ => Err(Error::WrongType(t, &[Type::Null, Type::String, Type::MapStart])), + } + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let byte = self.input.peek_byte()?; + let t = Type::try_from(byte)?; + match t { + Type::Null | Type::BooleanFalse | Type::BooleanTrue => { + _ = self.input.read_byte()?; + } + Type::UnsignedInt | Type::SignedInt => { + _ = self.input.read_byte()?; + while self.input.read_byte()? & 0x80 != 0 {} + } + Type::Float16 => { + self.input.skip_bytes(3)?; // Also the previous type byte. + } + Type::Float32 => { + self.input.skip_bytes(5)?; // Also the previous type byte. + } + Type::Float64 => { + self.input.skip_bytes(9)?; // Also the previous type byte. + } + Type::Float128 => { + self.input.skip_bytes(17)?; // Also the previous type byte. + } + Type::Bytes | Type::String => { + _ = self.input.read_byte()?; + let len = usize::decode(&mut self.input)?; + self.input.skip_bytes(len)?; + } + Type::SeqStart => return self.deserialize_seq(visitor), + Type::MapStart => return self.deserialize_map(visitor), + Type::SeqEnd | Type::MapEnd => { + return Err(Error::WrongType( + t, + &[ + Type::Null, + Type::BooleanFalse, + Type::BooleanTrue, + Type::UnsignedInt, + Type::SignedInt, + Type::Float16, + Type::Float32, + Type::Float64, + Type::Float128, + Type::Bytes, + Type::String, + Type::SeqStart, + Type::MapStart, + ], + )) + } + } + visitor.visit_unit() + } +} + +/// Deserialize sequence elements until the end of the sequence. +#[derive(Debug)] +pub struct SequenceDeserializer<'a, I, B>(&'a mut Deserializer); + +impl<'a, 'de, I, B> ::serde::de::SeqAccess<'de> for SequenceDeserializer<'a, I, B> +where + I: Input<'de>, + B: Buffer, +{ + type Error = Error; + + #[inline] + fn size_hint(&self) -> Option { + None + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: ::serde::de::DeserializeSeed<'de>, + { + let byte = self.0.input.peek_byte()?; + let t = Type::try_from(byte)?; + if t == Type::SeqEnd { + return Ok(None); + } + + seed.deserialize(&mut *self.0).map(Some) + } +} + +/// Deserialize into a sequence of bytes. +#[derive(Debug)] +pub struct ByteSequenceDeserializer<'a>(&'a [u8]); + +impl<'a, 'de> ::serde::de::SeqAccess<'de> for ByteSequenceDeserializer<'a> { + type Error = Error; + + #[inline] + fn size_hint(&self) -> Option { + Some(self.0.len()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: ::serde::de::DeserializeSeed<'de>, + { + if let Some((byte, remaining)) = self.0.split_first() { + self.0 = remaining; + seed.deserialize(byte.into_deserializer()).map(Some) + } else { + Ok(None) + } + } +} + +/// Deserialize into a sequence of [char]s. +#[derive(Debug)] +pub struct CharSequenceDeserializer<'a>(::core::str::Chars<'a>); + +impl<'a, 'de> ::serde::de::SeqAccess<'de> for CharSequenceDeserializer<'a> { + type Error = Error; + + #[inline] + fn size_hint(&self) -> Option { + None + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: ::serde::de::DeserializeSeed<'de>, + { + if let Some(c) = self.0.next() { + seed.deserialize(c.into_deserializer()).map(Some) + } else { + Ok(None) + } + } +} + +/// Deserialize map entries until the end of the map. +#[derive(Debug)] +pub struct MapDeserializer<'a, I, B>(&'a mut Deserializer); + +impl<'a, 'de, I, B> ::serde::de::MapAccess<'de> for MapDeserializer<'a, I, B> +where + I: Input<'de>, + B: Buffer, +{ + type Error = Error; + + #[inline] + fn size_hint(&self) -> Option { + None + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: ::serde::de::DeserializeSeed<'de>, + { + let byte = self.0.input.peek_byte()?; + let t = Type::try_from(byte)?; + if t == Type::MapEnd { + return Ok(None); + } + + seed.deserialize(&mut *self.0).map(Some) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_value_seed(&mut self, seed: V) -> Result + where + V: ::serde::de::DeserializeSeed<'de>, + { + seed.deserialize(&mut *self.0) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + #[allow(clippy::type_complexity, reason = "Tracing makes this trigger, also it's serde trait")] + fn next_entry_seed( + &mut self, + kseed: K, + vseed: V, + ) -> Result, Self::Error> + where + K: ::serde::de::DeserializeSeed<'de>, + V: ::serde::de::DeserializeSeed<'de>, + { + if let Some(key) = self.next_key_seed(kseed)? { + let value = self.next_value_seed(vseed)?; + Ok(Some((key, value))) + } else { + Ok(None) + } + } +} + +/// Deserialize enum variants. +#[derive(Debug)] +pub struct EnumMapDeserializer<'a, I, B>(&'a mut Deserializer); + +impl<'a, 'de, I, B> ::serde::de::EnumAccess<'de> for EnumMapDeserializer<'a, I, B> +where + I: Input<'de>, + B: Buffer, +{ + type Error = Error; + type Variant = Self; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: ::serde::de::DeserializeSeed<'de>, + { + // The `deserialize_enum` method parsed the map start so we are currently inside of a map. + // The seed will be deserializing itself from the key of the map. + let value = seed.deserialize(&mut *self.0)?; + Ok((value, self)) + } +} + +impl<'a, 'de, I, B> ::serde::de::VariantAccess<'de> for EnumMapDeserializer<'a, I, B> +where + I: Input<'de>, + B: Buffer, +{ + type Error = Error; + + // If the `Visitor` expected this variant to be a unit variant, the input + // should have been the plain string case handled in `deserialize_enum`. + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn unit_variant(self) -> Result<(), Self::Error> { + let byte = self.0.input.peek_byte()?; + let t = Type::try_from(byte)?; + let found = match t { + Type::SeqStart => Unexpected::TupleVariant, + Type::MapStart => Unexpected::StructVariant, + _ => Unexpected::NewtypeVariant, + }; + Err(::serde::de::Error::invalid_type(found, &"unit variant")) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn newtype_variant_seed(self, seed: T) -> Result + where + T: ::serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self.0) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + ::serde::de::Deserializer::deserialize_seq(self.0, visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn struct_variant( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + ::serde::de::Deserializer::deserialize_map(self.0, visitor) + } +} diff --git a/src/docs/mod.rs b/src/docs/mod.rs new file mode 100644 index 0000000..c428867 --- /dev/null +++ b/src/docs/mod.rs @@ -0,0 +1,7 @@ +//! Here lives more detailed documentation and explanations for this crate and its binary format. +//! +//! - [Format Specification](./format/index.html) + +pub mod format { + #![doc = include_str!("../../docs/format-specification.md")] +} diff --git a/src/error.rs b/src/error.rs index dd7c28f..8dd316e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1 +1,156 @@ //! Crate errors. + +use ::core::fmt::Display; + +use crate::format::Type; + +/// Error when (de-)serializing. +#[derive(Debug)] +pub enum Error { + /// Expected more data but encountered the end of the input. + UnexpectedEnd, + /// Excess data appeared at the end of the input. + ExcessData, + /// Buffer was too small. + BufferTooSmall, + /// Allocation failure. + Allocation, + /// Usize overflow. + UsizeOverflow, + /// Configured size limit reached. + LimitReached, + + /// Invalid data type designator encountered. + InvalidType(u8), + /// VarInt too large for the given expected type. + VarIntTooLarge, + /// Wrong data type encountered (found, expected). + WrongType(Type, &'static [Type]), + /// String is not exactly one character. + NotOneChar, + + /// Formatting error. Happens serializing a `core::fmt::Display` value and could be due to an + /// output writing failure. + Format(::core::fmt::Error), + /// Parsed string is not valid UTF-8. + StringNotUtf8(::core::str::Utf8Error), + /// IO error. + #[cfg(feature = "std")] + Io(::std::io::Error), + + /// **no-std + no-alloc**: Generic error message that can be created by data structures through + /// the `ser::Error` and `de::Error` traits. + Custom, + /// **alloc**: Generic error message that can be created by data structures through the + /// `ser::Error` and `de::Error` traits. + #[cfg(feature = "alloc")] + Message(::alloc::string::String), +} + +impl Display for Error { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Error::UnexpectedEnd => { + write!(f, "Expected more data but encountered the end of the input") + } + Error::ExcessData => write!(f, "Excess data appeared at the end of the input"), + Error::BufferTooSmall => write!(f, "Output or scratch buffer was too small"), + Error::Allocation => write!(f, "Allocator failed on allocating more space"), + Error::UsizeOverflow => write!(f, "Tried using more bytes than usize allows for"), + Error::LimitReached => write!(f, "Configured size limit reached"), + + Error::InvalidType(v) => { + write!(f, "Invalid data type designator encountered: {v:#02X}") + } + Error::VarIntTooLarge => write!(f, "VarInt too large for the given expected type"), + Error::WrongType(found, expected) => write!( + f, + "Wrong data type encountered. Found `{found:?}`, but expected one of `{expected:?}`" + ), + Error::NotOneChar => write!(f, "String is not exactly one character"), + + Error::Format(err) => write!(f, "Value formatting error: {err:#}"), + Error::StringNotUtf8(err) => write!(f, "String is not valid UTF-8: {err:#}"), + #[cfg(feature = "std")] + Error::Io(err) => write!(f, "IO error: {err:#}"), + + Error::Custom => write!(f, "Unknown custom error"), + #[cfg(feature = "alloc")] + Error::Message(msg) => write!(f, "Custom error: {msg}"), + } + } +} + +impl ::core::error::Error for Error { + fn source(&self) -> Option<&(dyn ::core::error::Error + 'static)> { + match self { + Error::Format(err) => Some(err), + Error::StringNotUtf8(err) => Some(err), + #[cfg(feature = "std")] + Error::Io(err) => Some(err), + _ => None, + } + } +} + +impl From<::core::fmt::Error> for Error { + #[inline] + fn from(err: ::core::fmt::Error) -> Self { + Self::Format(err) + } +} + +impl From<::core::str::Utf8Error> for Error { + #[inline] + fn from(err: ::core::str::Utf8Error) -> Self { + Self::StringNotUtf8(err) + } +} + +#[cfg(feature = "std")] +impl From<::std::io::Error> for Error { + #[inline] + fn from(err: ::std::io::Error) -> Self { + Self::Io(err) + } +} + +impl ::serde::ser::Error for Error { + #[cfg(not(feature = "alloc"))] + #[inline] + fn custom(_msg: T) -> Self + where + T: Display, + { + Self::Custom + } + + #[cfg(feature = "alloc")] + #[inline] + fn custom(msg: T) -> Self + where + T: Display, + { + Self::Message(::alloc::format!("{msg}")) + } +} + +impl ::serde::de::Error for Error { + #[cfg(not(feature = "alloc"))] + #[inline] + fn custom(_msg: T) -> Self + where + T: Display, + { + Self::Custom + } + + #[cfg(feature = "alloc")] + #[inline] + fn custom(msg: T) -> Self + where + T: Display, + { + Self::Message(::alloc::format!("{msg}")) + } +} diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..d2fd779 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,350 @@ +//! Data format internals. + +use crate::{ + io::{Input, Output}, + Error, Result, +}; + +/// The binary type identifier. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum Type { + /// The `null` or unit or none type. There is no additional byte value. + Null = 0, + /// The `boolean` type with value false. There is no additional byte value. + BooleanFalse = 1, + /// The `boolean` type with value true. There is no additional byte value. + BooleanTrue = 2, + /// The always-positive `integer` type of any length in variable length encoding. + /// + /// Format: Next bytes are the `VarInt` encoding of the unsigned number. The most-significant + /// bit of each byte says whether there is a next byte. The bytes are in little-endian oder, so + /// the first byte contains the least significant bits. + UnsignedInt = 3, + /// The signed `integer` type. + /// + /// Format: Next bytes are the `VarInt` encoding of the absolute number. The most-significant + /// bit of each byte says whether there is a next byte. The bytes are in little-endian oder, so + /// the first byte contains the least significant bits. The least-significant bit in the first + /// byte determines whether the value is negative or positive (the sign, 1 = negative). + SignedInt = 4, + /// The `float16` type. The next 2 bytes are the value. + Float16 = 5, + /// The `float32` type. The next 4 bytes are the value. + Float32 = 6, + /// The `float64` type. The next 8 bytes are the value. + Float64 = 7, + /// The `float128` type. The next 16 bytes are the value. + Float128 = 8, + /// The `bytes` type of length N. + /// + /// Format: The first bytes are an `UnsignedInt` that encodes the length N. Then N bytes data + /// follow. + Bytes = 10, + /// The `string` type. + /// + /// Format: Same as bytes, but all bytes must be valid UTF-8. + String = 11, + /// The `sequence` type consists of start, data and end. This is the start designator. + /// + /// Format: Any number of elements follow, then the end designator. + SeqStart = 15, + /// The end designator for the `sequence` type. + SeqEnd = 16, + /// The `map` type consists of start, data and end. This is the start designator. + /// + /// Format: Any number of elements follow, consisting of first key, then value. The end + /// designator finishes up the map. + MapStart = 17, + /// The end designator for the `map` type. + MapEnd = 18, +} + +impl From for u8 { + #[inline] + fn from(value: Type) -> Self { + value as u8 + } +} + +impl TryFrom for Type { + type Error = crate::Error; + + #[inline] + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Null), + 1 => Ok(Self::BooleanFalse), + 2 => Ok(Self::BooleanTrue), + 3 => Ok(Self::UnsignedInt), + 4 => Ok(Self::SignedInt), + 5 => Ok(Self::Float16), + 6 => Ok(Self::Float32), + 7 => Ok(Self::Float64), + 8 => Ok(Self::Float128), + 10 => Ok(Self::Bytes), + 11 => Ok(Self::String), + 15 => Ok(Self::SeqStart), + 16 => Ok(Self::SeqEnd), + 17 => Ok(Self::MapStart), + 18 => Ok(Self::MapEnd), + _ => Err(crate::Error::InvalidType(value)), + } + } +} + +/// The variable-length integer encoding implementation. +pub trait VarInt: Sized { + /// Encode the integer into bytes. + fn encode(&self, output: &mut O) -> Result<()>; + /// Decode the integer from bytes. + fn decode<'de, I: Input<'de>>(input: &mut I) -> Result; + + /// The maximum number of bytes needed to represent to var int. + const MAX_BYTES: usize = varint_max::(); +} + +/// Implement [VarInt] encoding for unsigned integers. +macro_rules! impl_var_int_unsigned { + ($($t:ty),*) => { + $( + impl VarInt for $t { + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn encode(&self, output: &mut O) -> Result<()> { + let mut value = *self; + for _ in 0..varint_max::<$t>() { + let byte = value.to_le_bytes()[0]; + + if value < 0x80 { + output.write_byte(byte)?; + return Ok(()); + } + + output.write_byte(byte | 0x80)?; + value >>= 7; + } + panic!("VarInt needed more than maximum bytes"); + } + + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn decode<'de, I: Input<'de>>(input: &mut I) -> Result { + let mut value = 0; + let mut bits = <$t>::BITS; + for i in 0..varint_max::<$t>() { + let byte = input.read_byte()?; + + if bits < 8 && ((byte & 0x7F) >> bits) != 0 { + return Err(Error::VarIntTooLarge); + } + bits = bits.saturating_sub(7); + + value |= (<$t>::from(byte & 0x7F)) << (i * 7); + if byte & 0x80 == 0 { + return Ok(value); + } + } + Err(Error::VarIntTooLarge) + } + } + )* + }; +} +impl_var_int_unsigned!(u8, u16, u32, u64, u128, usize); + +/// Implement [VarInt] encoding for signed integers. +macro_rules! impl_var_int_signed { + ($($u:ty => $t:ty),*) => { + $( + impl VarInt for $t { + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn encode(&self, output: &mut O) -> Result<()> { + let value = if self.is_negative() { + self.rotate_left(1).wrapping_neg() + } else { + self.rotate_left(1) + } as $u; + <$u>::encode(&value, output) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn decode<'de, I: Input<'de>>(input: &mut I) -> Result { + #[allow(clippy::cast_possible_wrap, reason = "Wrapping is intended")] + let value = <$u>::decode(input)? as $t; + if (value & 1) != 0 { + Ok(value.wrapping_neg().rotate_right(1)) + } else { + Ok(value.rotate_right(1)) + } + } + } + )* + }; +} +impl_var_int_signed!(u8 => i8, u16 => i16, u32 => i32, u64 => i64, u128 => i128, usize => isize); + +/// Returns the maximum number of bytes required to encode T. +pub const fn varint_max() -> usize { + let bits = ::core::mem::size_of::() * 8; + (bits + 6) / 7 +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, reason = "Tests")] + + use super::*; + + #[test] + fn type_conversion_works() { + let valid_types = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 15, 16, 17, 18]; + for byte in 0 ..= u8::MAX { + match Type::try_from(byte) { + Ok(t) => { + assert!( + valid_types.contains(&byte), + "Type {t:?} should should have been recognized from {byte} here" + ); + assert_eq!(u8::from(t), byte); + } + Err(_) => assert!( + !valid_types.contains(&byte), + "Type should have been recognized from {byte}" + ), + } + } + } + + + #[test] + fn unsigned_varint_encode_works() { + let mut bytes = [0; 1]; + let mut output = bytes.as_mut_slice(); + 0_u8.encode(&mut output).unwrap(); + assert_eq!(bytes, [0]); + let mut output = bytes.as_mut_slice(); + 0x7F_u8.encode(&mut output).unwrap(); + assert_eq!(bytes, [0x7F]); + let mut output = bytes.as_mut_slice(); + let result = 0xFF_u8.encode(&mut output); + assert!(matches!(result, Err(Error::BufferTooSmall))); + + let mut bytes = [0; 10]; + let mut output = bytes.as_mut_slice(); + 0xFF_u8.encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 2], &[0xFF, 0x01]); + let mut output = bytes.as_mut_slice(); + 0xFF_usize.encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 2], &[0xFF, 0x01]); + + let mut bytes = [0; u32::MAX_BYTES]; + let mut output = bytes.as_mut_slice(); + 64_u32.encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 1], &[0x40]); + let mut output = bytes.as_mut_slice(); + 0xFFFF_FFFF_u32.encode(&mut output).unwrap(); + assert_eq!(&bytes, &[0xFF, 0xFF, 0xFF, 0xFF, 0x0F]); + let mut output = bytes.as_mut_slice(); + 0x0196_0713_u32.encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 4], &[0x93, 0x8E, 0xD8, 0x0C]); + } + + #[test] + fn unsigned_varint_decode_works() { + let bytes = &[0x00, 0x00]; + let mut input = bytes.as_slice(); + let value = u16::decode(&mut input).unwrap(); + assert_eq!(input.len(), 1); // Only one byte read. + assert_eq!(value, 0); + + let bytes = &[0x80, 0x80, 0x00]; + let value = u16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, 0); + + let bytes = &[0x80, 0x80, 0x80, 0x00]; + let result = u16::decode(&mut bytes.as_slice()); + assert!(matches!(result, Err(Error::VarIntTooLarge))); + + let bytes = &[0xFF, 0xFF, 0x03]; + let value = u16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, 65535); + + let bytes = &[0xFF, 0xFF, 0x07]; + let result = u16::decode(&mut bytes.as_slice()); + assert!(matches!(result, Err(Error::VarIntTooLarge))); + } + + #[test] + fn signed_varint_encode_works() { + let mut bytes = [0; 1]; + let mut output = bytes.as_mut_slice(); + 0_i8.encode(&mut output).unwrap(); + assert_eq!(bytes, [0]); + let mut output = bytes.as_mut_slice(); + (-1_i8).encode(&mut output).unwrap(); + assert_eq!(bytes, [0x01]); + let mut output = bytes.as_mut_slice(); + (1_i8).encode(&mut output).unwrap(); + assert_eq!(bytes, [0x02]); + let mut output = bytes.as_mut_slice(); + let result = (64_i8).encode(&mut output); + assert!(matches!(result, Err(Error::BufferTooSmall))); + + let mut bytes = [0; 10]; + let mut output = bytes.as_mut_slice(); + (64_i8).encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 2], &[0x80, 0x01]); + let mut output = bytes.as_mut_slice(); + (-65_i8).encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 2], &[0x81, 0x01]); + let mut output = bytes.as_mut_slice(); + (-65_isize).encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 2], &[0x81, 0x01]); + + let mut bytes = [0; i32::MAX_BYTES]; + let mut output = bytes.as_mut_slice(); + 32767_i32.encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 3], &[0xFE, 0xFF, 0x03]); + let mut output = bytes.as_mut_slice(); + (-32768_i32).encode(&mut output).unwrap(); + assert_eq!(&bytes[0 .. 3], &[0xFF, 0xFF, 0x03]); + } + + #[test] + fn signed_varint_decode_works() { + let bytes = &[0x00, 0x00]; + let mut input = bytes.as_slice(); + let value = i16::decode(&mut input).unwrap(); + assert_eq!(input.len(), 1); // Only one byte read. + assert_eq!(value, 0); + + let bytes = &[0x80, 0x80, 0x00]; + let value = i16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, 0); + + let bytes = &[0x80, 0x80, 0x80, 0x00]; + let result = i16::decode(&mut bytes.as_slice()); + assert!(matches!(result, Err(Error::VarIntTooLarge))); + + let bytes = &[0x80, 0x01]; + let value = i16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, 64); + + let bytes = &[0x81, 0x01]; + let value = i16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, -65); + + let bytes = &[0xFE, 0xFF, 0x03]; + let value = i16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, 32767); + + let bytes = &[0xFF, 0xFF, 0x03]; + let value = i16::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(value, -32768); + + let bytes = &[0xFF, 0xFF, 0x07]; + let result = i16::decode(&mut bytes.as_slice()); + assert!(matches!(result, Err(Error::VarIntTooLarge))); + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..3799a5a --- /dev/null +++ b/src/io.rs @@ -0,0 +1,586 @@ +//! Implementation of input and output: reading and writing bytes. + +#[cfg(feature = "std")] +use ::std::io::{Read, Write}; + +use crate::{buffer::Buffer, Error, Result}; + +/// Generic interface for reading bytes from somewhere. +pub trait Input<'de> { + /// Peek at the next byte without consuming it. + fn peek_byte(&mut self) -> Result; + /// Read a single byte. + fn read_byte(&mut self) -> Result; + /// Read exactly the required number of bytes to fill the given buffer. + fn read_exact(&mut self, buffer: &mut [u8]) -> Result<()>; + /// Read (exactly) the given number of bytes. When possible, return the borrowed slice of the + /// input. If this is not possible, return `None` instead and write the output to the given + /// buffer. If the buffer does not exist, we are out of luck and need to return an error. + fn read_bytes(&mut self, len: usize, buffer: Option<&mut B>) -> Result> + where + B: Buffer; + /// Skip the given number of bytes. + fn skip_bytes(&mut self, len: usize) -> Result<()>; +} + +impl<'de> Input<'de> for &'de [u8] { + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn peek_byte(&mut self) -> Result { + self.first().copied().ok_or_else(|| Error::UnexpectedEnd) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_byte(&mut self) -> Result { + let (byte, remaining) = self.split_first().ok_or_else(|| Error::UnexpectedEnd)?; + *self = remaining; + Ok(*byte) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_exact(&mut self, buffer: &mut [u8]) -> Result<()> { + let (slice, remaining) = + self.split_at_checked(buffer.len()).ok_or_else(|| Error::UnexpectedEnd)?; + *self = remaining; + buffer.copy_from_slice(slice); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn read_bytes(&mut self, len: usize, _buffer: Option<&mut B>) -> Result> { + let (slice, remaining) = self.split_at_checked(len).ok_or_else(|| Error::UnexpectedEnd)?; + *self = remaining; + Ok(Some(slice)) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn skip_bytes(&mut self, len: usize) -> Result<()> { + let (_slice, remaining) = self.split_at_checked(len).ok_or_else(|| Error::UnexpectedEnd)?; + *self = remaining; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<'de, R> Input<'de> for IoReader +where + R: Read, +{ + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn peek_byte(&mut self) -> Result { + let byte = Input::read_byte(self)?; + self.next_byte = Some(byte); + Ok(byte) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_byte(&mut self) -> Result { + if let Some(byte) = self.next_byte.take() { + Ok(byte) + } else { + let mut bytes = self.reader.by_ref().bytes(); + let byte = bytes.next().ok_or_else(|| Error::UnexpectedEnd)??; + Ok(byte) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_exact(&mut self, mut buffer: &mut [u8]) -> Result<()> { + if buffer.is_empty() { + return Ok(()); + } + + if let Some(byte) = self.next_byte.take() { + let (first, remaining) = + buffer.split_first_mut().ok_or_else(|| Error::BufferTooSmall)?; + *first = byte; + buffer = remaining; + } + + match self.reader.read_exact(buffer) { + Err(err) if err.kind() == ::std::io::ErrorKind::UnexpectedEof => { + return Err(Error::UnexpectedEnd) + } + res => res?, + } + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn read_bytes(&mut self, mut len: usize, buffer: Option<&mut B>) -> Result> + where + B: Buffer, + { + if len == 0 { + return Ok(Some(&[])); + } + + let buffer = buffer.ok_or_else(|| Error::BufferTooSmall)?; + if let Some(byte) = self.next_byte.take() { + buffer.push(byte)?; + len -= 1; + } + + let write = buffer.reserve_slice(len)?; + match self.reader.read_exact(write) { + Err(err) if err.kind() == ::std::io::ErrorKind::UnexpectedEof => { + return Err(Error::UnexpectedEnd) + } + res => res?, + } + Ok(None) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn skip_bytes(&mut self, mut len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + + if self.next_byte.take().is_some() { + len -= 1; + } + + #[expect(clippy::expect_used, reason = "Fundamental architecture assumption")] + let to_write = u64::try_from(len).expect("usize is smaller or equal to u64"); + let mut skip = self.reader.by_ref().take(to_write); + let result = ::std::io::copy(&mut skip, &mut ::std::io::sink()); + match result { + Err(err) if err.kind() == ::std::io::ErrorKind::UnexpectedEof => { + return Err(Error::UnexpectedEnd) + } + Ok(bytes) if bytes != to_write => return Err(Error::UnexpectedEnd), + res => { + res?; + } + } + Ok(()) + } +} + + +/// Generic interface for writing bytes to somewhere. +pub trait Output { + /// Write a single byte. + fn write_byte(&mut self, byte: u8) -> Result<()>; + /// Write all bytes from the buffer. + fn write_all(&mut self, bytes: &[u8]) -> Result<()>; +} + +impl Output for &mut [u8] { + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(byte)))] + fn write_byte(&mut self, byte: u8) -> Result<()> { + // This somehow makes more optimized code gen. + if self.is_empty() { + return Err(Error::BufferTooSmall); + } + + let (write, remaining) = + ::core::mem::take(self).split_first_mut().ok_or_else(|| Error::BufferTooSmall)?; + *write = byte; + *self = remaining; + + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn write_all(&mut self, bytes: &[u8]) -> Result<()> { + // This somehow makes more optimized code gen. + if self.is_empty() { + return Err(Error::BufferTooSmall); + } + + let (write, remaining) = ::core::mem::take(self) + .split_at_mut_checked(bytes.len()) + .ok_or_else(|| Error::BufferTooSmall)?; + write.copy_from_slice(bytes); + *self = remaining; + + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl Output for ::alloc::vec::Vec { + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(byte)))] + fn write_byte(&mut self, byte: u8) -> Result<()> { + self.push(byte); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn write_all(&mut self, bytes: &[u8]) -> Result<()> { + self.extend_from_slice(bytes); + Ok(()) + } +} + +#[cfg(feature = "heapless")] +impl Output for ::heapless::Vec { + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(byte)))] + fn write_byte(&mut self, byte: u8) -> Result<()> { + self.push(byte).map_err(|_| Error::BufferTooSmall) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn write_all(&mut self, bytes: &[u8]) -> Result<()> { + self.extend_from_slice(bytes).map_err(|_| Error::BufferTooSmall) + } +} + +// Note: this is also implementing for `std::vec::Vec`. +#[cfg(feature = "std")] +impl Output for IoWriter +where + W: Write, +{ + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(byte)))] + fn write_byte(&mut self, byte: u8) -> Result<()> { + self.writer.write_all(&[byte])?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn write_all(&mut self, bytes: &[u8]) -> Result<()> { + self.writer.write_all(bytes)?; + Ok(()) + } +} + + +/// Wrapper for generic reader types as [Output]. +#[allow(dead_code, reason = "Different feature sets")] +#[derive(Debug)] +pub struct IoReader { + /// The inner reader. + reader: R, + /// Next peeked byte if available. + next_byte: Option, +} + +#[allow(dead_code, reason = "Different feature sets")] +impl IoReader { + /// Create a new reader from the given reader. + #[must_use] + pub const fn new(reader: R) -> Self { + Self { reader, next_byte: None } + } +} + +/// Wrapper for generic writer types as [Output]. +#[allow(dead_code, reason = "Different feature sets")] +#[derive(Debug)] +pub struct IoWriter { + /// The inner writer. + writer: W, +} + +#[allow(dead_code, reason = "Different feature sets")] +impl IoWriter { + /// Create a new writer from the given writer. + #[must_use] + pub const fn new(writer: W) -> Self { + Self { writer } + } +} + +/// [Input]/[Output] wrapper that limits the number of bytes being read/written. +pub struct SizeLimit { + /// The inner input/output. + inner: IO, + /// The remaining number of bytes that can be read/written. + limit: usize, +} + +impl SizeLimit { + /// Create a new size limit from the given input/output and limit. + #[must_use] + pub const fn new(inner: IO, limit: usize) -> Self { + Self { inner, limit } + } + + /// Consume the size limit and return the inner input/output. + #[must_use] + pub fn into_inner(self) -> IO { + self.inner + } +} + +impl<'de, I> Input<'de> for SizeLimit +where + I: Input<'de>, +{ + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn peek_byte(&mut self) -> Result { + if self.limit == 0 { + return Err(Error::LimitReached); + } + + self.inner.peek_byte() + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_byte(&mut self) -> Result { + if self.limit == 0 { + return Err(Error::LimitReached); + } + self.limit -= 1; + + self.inner.read_byte() + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn read_exact(&mut self, buffer: &mut [u8]) -> Result<()> { + if self.limit < buffer.len() { + return Err(Error::LimitReached); + } + self.limit -= buffer.len(); + + self.inner.read_exact(buffer) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn read_bytes(&mut self, len: usize, buffer: Option<&mut B>) -> Result> + where + B: Buffer, + { + if self.limit < len { + return Err(Error::LimitReached); + } + self.limit -= len; + + self.inner.read_bytes(len, buffer) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(len)))] + fn skip_bytes(&mut self, len: usize) -> Result<()> { + if self.limit < len { + return Err(Error::LimitReached); + } + self.limit -= len; + + self.inner.skip_bytes(len) + } +} + +impl Output for SizeLimit +where + O: Output, +{ + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(byte)))] + fn write_byte(&mut self, byte: u8) -> Result<()> { + if self.limit == 0 { + return Err(Error::LimitReached); + } + self.limit -= 1; + + self.inner.write_byte(byte) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn write_all(&mut self, bytes: &[u8]) -> Result<()> { + if self.limit < bytes.len() { + return Err(Error::LimitReached); + } + self.limit -= bytes.len(); + + self.inner.write_all(bytes) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, reason = "Tests")] + + use super::*; + + const PANIC_INPUT_DATA: &[u8] = &[0]; + fn input_does_not_panic<'de, I: Input<'de>>(mut input: I) { + _ = input.peek_byte(); + _ = input.read_byte(); + _ = input.read_exact(&mut [0, 1, 2, 3, 4]); + _ = input.read_bytes::<()>(10, None); + _ = input.read_bytes(10, Some(&mut ())); + _ = input.read_bytes::<()>(usize::MAX / 2, None); + _ = input.read_bytes::<()>(usize::MAX, None); + _ = input.skip_bytes(10); + _ = input.skip_bytes(usize::MAX / 2); + _ = input.skip_bytes(usize::MAX); + } + + const BASIC_INPUT_DATA: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + fn basic_input_works<'de, I: Input<'de>>(mut input: I) { + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 0); + let _byte = input.peek_byte().unwrap(); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 0); + + let byte = input.read_byte().unwrap(); + assert_eq!(byte, 0); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 1); + let _byte = input.read_byte().unwrap(); + let byte = input.read_byte().unwrap(); + assert_eq!(byte, 2); + + let mut target = [0; 0]; + input.read_exact(&mut target).unwrap(); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 3); + let mut target = [0; 2]; + input.read_exact(&mut target).unwrap(); + assert_eq!(target, [3, 4]); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 5); + + input.skip_bytes(0).unwrap(); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 5); + input.skip_bytes(1).unwrap(); + input.skip_bytes(2).unwrap(); + let byte = input.peek_byte().unwrap(); + assert_eq!(byte, 8); + + input.skip_bytes(2).unwrap(); + assert!(input.peek_byte().is_err()); + assert!(input.read_byte().is_err()); + assert!(input.read_exact(&mut [0]).is_err()); + assert!(input.skip_bytes(1).is_err()); + } + + const READ_BYTES_INPUT_DATA: &[u8] = &[5; 20]; + fn read_bytes_works<'de, I: Input<'de>, B: Buffer>(mut input: I, mut buffer: Option) { + if let Some(b) = buffer.as_mut() { + b.clear(); + } + + let borrowed = input.read_bytes(10, buffer.as_mut()).unwrap(); + let slice = borrowed.unwrap_or(buffer.as_ref().map_or(&[], |b| b.as_slice())); + assert_eq!(slice.len(), 10); + assert_eq!(slice, [5; 10].as_slice()); + + if let Some(b) = buffer.as_mut() { + b.clear(); + } + + let borrowed = input.read_bytes(5, buffer.as_mut()).unwrap(); + let slice = borrowed.unwrap_or(buffer.as_ref().map_or(&[], |b| b.as_slice())); + assert_eq!(slice.len(), 5); + assert_eq!(slice, [5; 5].as_slice()); + + if let Some(b) = buffer.as_mut() { + b.clear(); + } + + assert!(input.read_bytes(10, buffer.as_mut()).is_err()); + } + + #[test] + fn slice_input_behaves() { + input_does_not_panic(PANIC_INPUT_DATA); + basic_input_works(BASIC_INPUT_DATA); + read_bytes_works(READ_BYTES_INPUT_DATA, None::<()>); + + // Read bytes returns borrowed data. + let mut input = READ_BYTES_INPUT_DATA; + let mut buffer = None::<()>; + let borrowed = input.read_bytes(10, buffer.as_mut()).unwrap(); + assert!(borrowed.is_some()); + } + + #[cfg(feature = "std")] + #[test] + fn reader_input_behaves() { + input_does_not_panic(IoReader::new(PANIC_INPUT_DATA)); + basic_input_works(IoReader::new(BASIC_INPUT_DATA)); + read_bytes_works(IoReader::new(READ_BYTES_INPUT_DATA), Some(Vec::new())); + + // Buffer behavior from IO reader. + let mut input = IoReader::new(READ_BYTES_INPUT_DATA); + let mut buffer = Some(Vec::new()); + _ = input.read_bytes(10, buffer.as_mut()).unwrap(); + _ = input.read_bytes(5, buffer.as_mut()).unwrap(); + assert_eq!(buffer.unwrap().len(), 15); + } + + + fn output_does_not_panic(mut output: O) { + _ = output.write_byte(0); + _ = output.write_all(&[]); + _ = output.write_all(&[1]); + _ = output.write_all(&[1, 2, 3, 4, 5]); + } + + const BASIC_OUTPUT_DATA: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + fn basic_output_works(output: &mut O) { + output.write_byte(0).unwrap(); + output.write_byte(1).unwrap(); + output.write_all(&[]).unwrap(); + output.write_all(&[2, 3, 4, 5]).unwrap(); + output.write_byte(6).unwrap(); + output.write_all(&[7, 8, 9]).unwrap(); + } + + #[test] + fn slice_output_behaves() { + output_does_not_panic([1, 2].as_mut_slice()); + let mut buffer = [0; 10]; + let mut output = buffer.as_mut_slice(); + basic_output_works(&mut output); + let expected: &mut [u8] = &mut []; + assert_eq!(output, expected); + assert_eq!(buffer, BASIC_OUTPUT_DATA); + } + + #[cfg(feature = "alloc")] + #[test] + fn vec_output_behaves() { + output_does_not_panic(::alloc::vec::Vec::new()); + let mut output = ::alloc::vec::Vec::new(); + basic_output_works(&mut output); + assert_eq!(&output, BASIC_OUTPUT_DATA); + } + + #[cfg(feature = "heapless")] + #[test] + fn heapless_output_behaves() { + output_does_not_panic(::heapless::Vec::<_, 2>::new()); + let mut output = ::heapless::Vec::<_, 10>::new(); + basic_output_works(&mut output); + assert_eq!(&output, BASIC_OUTPUT_DATA); + } + + #[cfg(feature = "std")] + #[test] + fn writer_output_behaves() { + output_does_not_panic(IoWriter::new(Vec::new())); + let mut output = IoWriter::new(Vec::new()); + basic_output_works(&mut output); + assert_eq!(&output.writer, BASIC_OUTPUT_DATA); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f971a6..7cb3b4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,297 @@ -//! TODO. +//! # Brief +//! +//! Brief (German for letter) is a crate for encoding and decoding data into a binary format that is self-descriptive and [serde](https://docs.rs/serde/)-compatible. +//! +//! ## Design Goals +//! +//! Not necessarily in order of importance: +//! +//! - Convenient to use for developers: Integrates into the Rust ecosystem via `serde`, supporting +//! all of its features in its derived implementations (e.g. renaming, flattening, ..). +//! - Compatibility: Easy to add or re-order fields/variants without breakage. +//! - `#![no_std]` and std compatible. +//! - Resource efficient: High performance, low memory usage. +//! - Interoperability: Different architectures can communicate flawlessly. +//! +//! ## More Detailed Documentation +//! +//! See more detailed documentation in [the docs module](./docs/index.html). It contains information +//! on the binary representation format. +//! +//! ## Feature Flags +//! +//! This library is both no-std and std compatible. Additionally, there are some other features to +//! enable additional functionality: +//! +//! | Feature Flag | Default | Description | +//! | --- | --- | --- | +//! | alloc | no | Enables the use of `alloc` types like serialization to a `Vec`. | +//! | heapless | no | Enables serialization to a `heapless::Vec`. | +//! | std | no | Enables the use of `std` types like serialization to a `Write`r and deserialization from a `Read`er. | +//! | tracing | no | Enables tracing instrumentation. | +//! +//! ## Flavors / Modes +//! +//! By default, structs' field names and enums' variant names are encoded as strings. This can be +//! configured to be encoded as unsigned integers of their indices instead. However, this has +//! compatibility implications and some serde features do not work with the index representation. +//! See the format specification for more info. +//! +//! ## Usage +//! +//! Add the library to your project with `cargo add brief`. By default, no features are enabled +//! (currently), so it is no-std by default. You can enable use of `Vec`s and such with features +//! like `alloc` or `std`. +//! +//! ### Example Serialization/Deserialization +//! +//! The `heapless` feature was enabled for this example. It is similarly possible with `std`'s `Vec` +//! or just slices. +//! +//! ```rust +//! use heapless::Vec; +//! use serde::{Deserialize, Serialize}; +//! +//! #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +//! struct MyBorrowedData<'a> { +//! name: &'a str, +//! age: u8, +//! } +//! +//! let data = MyBorrowedData { name: "Holla", age: 21 }; +//! let mut output: Vec = brief::to_heapless_vec(&data).unwrap(); +//! +//! assert_eq!( +//! output, +//! [ +//! 17, 11, 4, b'n', b'a', b'm', b'e', 11, 5, b'H', b'o', b'l', b'l', b'a', 11, 3, b'a', +//! b'g', b'e', 3, 21, 18 +//! ] +//! ); +//! +//! let parsed: MyBorrowedData = brief::from_slice(&output).unwrap(); +//! assert_eq!(parsed, data); +//! ``` +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "alloc")] +extern crate alloc; + +mod buffer; +mod config; pub mod de; +pub mod docs; mod error; +mod format; +mod io; pub mod ser; +#[cfg(feature = "alloc")] +pub mod value; + +#[allow(unused_imports, reason = "Different feature sets")] +use ::serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "std")] +use ::std::io::{Read, Write}; + +#[cfg(feature = "alloc")] +pub use self::value::{from_value, from_value_with_config, to_value, to_value_with_config}; +pub use self::{config::Config, de::Deserializer, error::Error, ser::Serializer}; + +/// `Result` type that uses the `brief` error. +pub type Result = ::core::result::Result; + +/// Serialize a type into a slice of bytes using the given configuration. Returns the slice with the +/// serialized data. +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn to_slice_with_config<'buf, T>( + value: &T, + buffer: &'buf mut [u8], + config: Config, +) -> Result<&'buf mut [u8]> +where + T: Serialize, +{ + let remaining = if let Some(max) = config.max_size { + let mut ser = Serializer::new(io::SizeLimit::new(&mut *buffer, max.into())) + .use_indices(config.use_indices); + value.serialize(&mut ser)?; + ser.into_output().into_inner().len() + } else { + let mut ser = Serializer::new(&mut *buffer).use_indices(config.use_indices); + value.serialize(&mut ser)?; + ser.into_output().len() + }; + + let used = buffer.len() - remaining; + Ok(buffer.split_at_mut(used).0) +} + +/// Serialize a type into a slice of bytes. Returns the slice with the serialized data. +pub fn to_slice<'buf, T>(value: &T, buffer: &'buf mut [u8]) -> Result<&'buf mut [u8]> +where + T: Serialize, +{ + to_slice_with_config(value, buffer, Config::default()) +} + +/// Serialize a type into a [Vec] of bytes using the given configuration. +#[cfg(feature = "alloc")] +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn to_vec_with_config(value: &T, config: Config) -> Result<::alloc::vec::Vec> +where + T: Serialize, +{ + if let Some(max) = config.max_size { + let mut ser = Serializer::new(io::SizeLimit::new(::alloc::vec::Vec::new(), max.into())) + .use_indices(config.use_indices); + value.serialize(&mut ser)?; + Ok(ser.into_output().into_inner()) + } else { + let mut ser = Serializer::new(::alloc::vec::Vec::new()).use_indices(config.use_indices); + value.serialize(&mut ser)?; + Ok(ser.into_output()) + } +} + +/// Serialize a type into a [Vec] of bytes. +#[cfg(feature = "alloc")] +pub fn to_vec(value: &T) -> Result<::alloc::vec::Vec> +where + T: Serialize, +{ + to_vec_with_config(value, Config::default()) +} + +/// Serialize a type into a [`heapless::Vec`] of bytes using the given configuration. +#[cfg(feature = "heapless")] +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn to_heapless_vec_with_config( + value: &T, + config: Config, +) -> Result<::heapless::Vec> +where + T: Serialize, +{ + if let Some(max) = config.max_size { + let mut ser = Serializer::new(io::SizeLimit::new(::heapless::Vec::new(), max.into())) + .use_indices(config.use_indices); + value.serialize(&mut ser)?; + Ok(ser.into_output().into_inner()) + } else { + let mut ser = Serializer::new(::heapless::Vec::new()).use_indices(config.use_indices); + value.serialize(&mut ser)?; + Ok(ser.into_output()) + } +} + +/// Serialize a type into a [`heapless::Vec`] of bytes. +#[cfg(feature = "heapless")] +pub fn to_heapless_vec(value: &T) -> Result<::heapless::Vec> +where + T: Serialize, +{ + to_heapless_vec_with_config(value, Config::default()) +} + +/// Serialize a type into a [Write]r using the given configuration. +#[cfg(feature = "std")] +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn to_writer_with_config(value: &T, writer: W, config: Config) -> Result<()> +where + T: Serialize, + W: Write, +{ + if let Some(max) = config.max_size { + let mut ser = Serializer::new(io::SizeLimit::new(io::IoWriter::new(writer), max.into())) + .use_indices(config.use_indices); + value.serialize(&mut ser)?; + } else { + let mut ser = Serializer::new(io::IoWriter::new(writer)).use_indices(config.use_indices); + value.serialize(&mut ser)?; + } + Ok(()) +} + +/// Serialize a type into a [Write]r. +#[cfg(feature = "std")] +pub fn to_writer(value: &T, writer: W) -> Result<()> +where + T: Serialize, + W: Write, +{ + to_writer_with_config(value, writer, Config::default()) +} + +/// Deserialize a type from a slice of bytes using the given configuration. +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn from_slice_with_config<'de, T>(bytes: &'de [u8], config: Config) -> Result +where + T: Deserialize<'de>, +{ + let error_on_excess = config.error_on_excess_data; + + let (value, peek) = if let Some(max) = config.max_size { + // The deserializer can parse both with and without `use_indices`.` + let mut de = Deserializer::new(io::SizeLimit::new(bytes, max.into())); + (T::deserialize(&mut de)?, io::Input::peek_byte(&mut de.into_input())) + } else { + // The deserializer can parse both with and without `use_indices`.` + let mut de = Deserializer::new(bytes); + (T::deserialize(&mut de)?, io::Input::peek_byte(&mut de.into_input())) + }; + + if error_on_excess && peek.is_ok() { + return Err(Error::ExcessData); + } + + Ok(value) +} + +/// Deserialize a type from a slice of bytes. +pub fn from_slice<'de, T>(bytes: &'de [u8]) -> Result +where + T: Deserialize<'de>, +{ + from_slice_with_config(bytes, Config::default()) +} + +/// Deserialize a type from a [Read]er using the given configuration. +#[cfg(feature = "std")] +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all, fields(config)))] +pub fn from_reader_with_config(reader: R, config: Config) -> Result +where + R: Read, + T: DeserializeOwned, +{ + let error_on_excess = config.error_on_excess_data; + + let (value, peek) = if let Some(max) = config.max_size { + // The deserializer can parse both with and without `use_indices`.` + let mut de = Deserializer::new(io::SizeLimit::new(io::IoReader::new(reader), max.into())) + .with_buffer(Vec::new()); + (T::deserialize(&mut de)?, io::Input::peek_byte(&mut de.into_input())) + } else { + // The deserializer can parse both with and without `use_indices`.` + let mut de = Deserializer::new(io::IoReader::new(reader)).with_buffer(Vec::new()); + (T::deserialize(&mut de)?, io::Input::peek_byte(&mut de.into_input())) + }; + + if error_on_excess && peek.is_ok() { + return Err(Error::ExcessData); + } + + Ok(value) +} + +/// Deserialize a type from a [Read]er. +#[cfg(feature = "std")] +pub fn from_reader(reader: R) -> Result +where + R: Read, + T: DeserializeOwned, +{ + from_reader_with_config(reader, Config::default()) +} + +#[cfg(test)] +mod tests; diff --git a/src/ser.rs b/src/ser.rs index f6dac2e..c1ddc5b 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1 +1,647 @@ //! Serialization implementation. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +use ::serde::Serialize; + +use crate::{ + format::{Type, VarInt}, + io::Output, + Config, Error, +}; + +/// The serializer for the binary format. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Serializer { + /// The output to write to. + output: O, + /// Serialize enum variants and struct fields by index instead of name-string. + use_indices: bool, +} + +impl Serializer { + /// Create a new serializer from any [Output] compatible type. + #[must_use] + pub fn new(output: O) -> Self + where + // Same bounds as `serde::Serializer` impl. + O: Output, + { + Self { output, use_indices: Config::default().use_indices } + } + + /// Set whether to use indices instead of names for enum variants and struct fields. + #[must_use] + pub const fn use_indices(mut self, use_indices: bool) -> Self { + self.use_indices = use_indices; + self + } + + /// Consume the serializer to get the output back. + #[inline] + pub fn into_output(self) -> O { + self.output + } +} + +impl<'a, O> ::serde::Serializer for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Self; + type SerializeMap = Self; + type SerializeStruct = StructSerializer<'a, O>; + type SerializeStructVariant = StructSerializer<'a, O>; + + #[inline] + fn is_human_readable(&self) -> bool { + false + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_bool(self, v: bool) -> Result { + if v { + self.output.write_byte(Type::BooleanTrue.into())?; + } else { + self.output.write_byte(Type::BooleanFalse.into())?; + } + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i8(self, v: i8) -> Result { + self.output.write_byte(Type::SignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i16(self, v: i16) -> Result { + self.output.write_byte(Type::SignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i32(self, v: i32) -> Result { + self.output.write_byte(Type::SignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i64(self, v: i64) -> Result { + self.output.write_byte(Type::SignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i128(self, v: i128) -> Result { + self.output.write_byte(Type::SignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u8(self, v: u8) -> Result { + self.output.write_byte(Type::UnsignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u16(self, v: u16) -> Result { + self.output.write_byte(Type::UnsignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u32(self, v: u32) -> Result { + self.output.write_byte(Type::UnsignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u64(self, v: u64) -> Result { + self.output.write_byte(Type::UnsignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u128(self, v: u128) -> Result { + self.output.write_byte(Type::UnsignedInt.into())?; + v.encode(&mut self.output)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_f32(self, v: f32) -> Result { + self.output.write_byte(Type::Float32.into())?; + self.output.write_all(&v.to_le_bytes())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_f64(self, v: f64) -> Result { + self.output.write_byte(Type::Float64.into())?; + self.output.write_all(&v.to_le_bytes())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_char(self, v: char) -> Result { + let mut buffer = [0; 4]; + let s = v.encode_utf8(&mut buffer); + self.serialize_str(s) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_str(self, v: &str) -> Result { + self.output.write_byte(Type::String.into())?; + let bytes = v.as_bytes(); + bytes.len().encode(&mut self.output)?; + self.output.write_all(bytes)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_bytes(self, v: &[u8]) -> Result { + self.output.write_byte(Type::Bytes.into())?; + v.len().encode(&mut self.output)?; + self.output.write_all(v)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_none(self) -> Result { + self.output.write_byte(Type::Null.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + serde::Serialize, + { + value.serialize(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit(self) -> Result { + self.output.write_byte(Type::Null.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.output.write_byte(Type::Null.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + if self.use_indices { + variant_index.serialize(self) + } else { + variant.serialize(self) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + value.serialize(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_newtype_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + use ::serde::ser::SerializeMap; + let use_indices = self.use_indices; + let mut map = self.serialize_map(Some(1))?; + if use_indices { + map.serialize_entry(&variant_index, value)?; + } else { + map.serialize_entry(variant, value)?; + } + map.end()?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_seq(self, _len: Option) -> Result { + self.output.write_byte(Type::SeqStart.into())?; + Ok(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple(self, _len: usize) -> Result { + self.output.write_byte(Type::SeqStart.into())?; + Ok(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + self.output.write_byte(Type::SeqStart.into())?; + Ok(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.output.write_byte(Type::MapStart.into())?; + if self.use_indices { + variant_index.serialize(&mut *self)?; + } else { + variant.serialize(&mut *self)?; + } + self.output.write_byte(Type::SeqStart.into())?; + Ok(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_map(self, _len: Option) -> Result { + self.output.write_byte(Type::MapStart.into())?; + Ok(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + self.output.write_byte(Type::MapStart.into())?; + Ok(StructSerializer::new(self)) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_struct_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.output.write_byte(Type::MapStart.into())?; + if self.use_indices { + variant_index.serialize(&mut *self)?; + } else { + variant.serialize(&mut *self)?; + } + self.output.write_byte(Type::MapStart.into())?; + Ok(StructSerializer::new(self)) + } + + #[cfg(feature = "alloc")] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn collect_str(self, value: &T) -> Result + where + T: ?Sized + core::fmt::Display, + { + let s = ::alloc::string::ToString::to_string(value); + self.serialize_str(&s) + } + + #[cfg(not(feature = "alloc"))] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn collect_str(self, value: &T) -> Result + where + T: ?Sized + core::fmt::Display, + { + use ::core::fmt::Write; + + /// A writer that counts the number of bytes written. + struct CountWriter(usize); + impl Write for CountWriter { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.0 += s.len(); + Ok(()) + } + } + + /// A writer that writes the formatted string into the output. + struct OutputWriter<'a, O>(&'a mut O); + impl<'a, O: Output> Write for OutputWriter<'a, O> { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.0.write_all(s.as_bytes()).map_err(|_| ::core::fmt::Error) + } + } + + // Pass through once to get the string length. + let mut counter = CountWriter(0); + write!(&mut counter, "{value}")?; + let len = counter.0; + self.output.write_byte(Type::String.into())?; + len.encode(&mut self.output)?; + + // Second pass to actually write the data. + let mut writer = OutputWriter(&mut self.output); + write!(&mut writer, "{value}")?; + + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeSeq for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + value.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.output.write_byte(Type::SeqEnd.into())?; + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeTuple for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + value.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.output.write_byte(Type::SeqEnd.into())?; + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeTupleStruct for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + value.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.output.write_byte(Type::SeqEnd.into())?; + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeTupleVariant for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + value.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.output.write_byte(Type::SeqEnd.into())?; + self.output.write_byte(Type::MapEnd.into())?; + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeMap for &'a mut Serializer +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + key.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + value.serialize(&mut **self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.output.write_byte(Type::MapEnd.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> + where + K: ?Sized + serde::Serialize, + V: ?Sized + serde::Serialize, + { + self.serialize_key(key)?; + self.serialize_value(value) + } +} + +/// Struct serializer that keeps track of the field index. +#[derive(Debug)] +pub struct StructSerializer<'a, O> { + /// The inner serializer. + serializer: &'a mut Serializer, + /// The current field index. + field_index: u32, +} + +impl<'a, O> StructSerializer<'a, O> { + /// Create a new struct serializer. + #[must_use] + fn new(serializer: &'a mut Serializer) -> Self { + Self { serializer, field_index: 0 } + } +} + +impl<'a, O> ::serde::ser::SerializeStruct for StructSerializer<'a, O> +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + if self.serializer.use_indices { + self.field_index.serialize(&mut *self.serializer)?; + } else { + key.serialize(&mut *self.serializer)?; + } + self.field_index += 1; + value.serialize(&mut *self.serializer) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.serializer.output.write_byte(Type::MapEnd.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn skip_field(&mut self, _key: &'static str) -> Result<(), Self::Error> { + self.field_index += 1; + Ok(()) + } +} + +impl<'a, O> ::serde::ser::SerializeStructVariant for StructSerializer<'a, O> +where + O: Output, +{ + type Ok = (); + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if self.serializer.use_indices { + self.field_index.serialize(&mut *self.serializer)?; + } else { + key.serialize(&mut *self.serializer)?; + } + self.field_index += 1; + value.serialize(&mut *self.serializer) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + self.serializer.output.write_byte(Type::MapEnd.into())?; + self.serializer.output.write_byte(Type::MapEnd.into())?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn skip_field(&mut self, _key: &'static str) -> Result<(), Self::Error> { + self.field_index += 1; + Ok(()) + } +} diff --git a/src/tests/basic_types.rs b/src/tests/basic_types.rs new file mode 100644 index 0000000..b2195e5 --- /dev/null +++ b/src/tests/basic_types.rs @@ -0,0 +1,931 @@ +//! Basic type serde tests. +#![allow(clippy::too_many_lines, reason = "Byte lists and such :P")] + +use ::serde_bytes::Bytes; + +use super::*; +use crate::format::Type; + +#[test] +fn test_unit() { + init_tracing(); + test_serde(&(), &mut [0; 1024]); + test_serde_with_indices(&(), &mut [0; 1024]); + test_deser::<()>(&[Type::Null.into()]); + test_deser_with_indices::<()>(&[Type::Null.into()]); +} + +#[test] +fn test_boolean() { + init_tracing(); + test_serde(&true, &mut [0; 1024]); + test_serde_with_indices(&true, &mut [0; 1024]); + test_deser::(&[Type::BooleanFalse.into()]); + test_deser_with_indices::(&[Type::BooleanFalse.into()]); + test_serde(&false, &mut [0; 1024]); + test_serde_with_indices(&false, &mut [0; 1024]); + test_deser::(&[Type::BooleanTrue.into()]); + test_deser_with_indices::(&[Type::BooleanTrue.into()]); +} + +#[test] +fn test_unsigned_int() { + init_tracing(); + test_serde(&125_u8, &mut [0; 1024]); + test_serde_with_indices(&125_u8, &mut [0; 1024]); + test_serde(&125_usize, &mut [0; 1024]); + test_serde_with_indices(&125_usize, &mut [0; 1024]); + test_serde(&125_u128, &mut [0; 1024]); + test_serde_with_indices(&125_u128, &mut [0; 1024]); + test_serde(&0x0123_4567_89AB_CDEF_u64, &mut [0; 1024]); + test_serde_with_indices(&0x0123_4567_89AB_CDEF_u64, &mut [0; 1024]); + test_deser::(&[Type::UnsignedInt.into(), 0x00]); + test_deser_with_indices::(&[Type::UnsignedInt.into(), 0x00]); + test_deser::(&[Type::UnsignedInt.into(), 0xFF, 0x01]); + test_deser_with_indices::(&[Type::UnsignedInt.into(), 0xFF, 0x01]); +} + +#[test] +fn test_signed_int() { + init_tracing(); + test_serde(&125_i8, &mut [0; 1024]); + test_serde_with_indices(&125_i8, &mut [0; 1024]); + test_serde(&-125_isize, &mut [0; 1024]); + test_serde_with_indices(&-125_isize, &mut [0; 1024]); + test_serde(&125_i128, &mut [0; 1024]); + test_serde_with_indices(&125_i128, &mut [0; 1024]); + test_serde(&0x0123_4567_89AB_CDEF_i64, &mut [0; 1024]); + test_serde_with_indices(&0x0123_4567_89AB_CDEF_i64, &mut [0; 1024]); + test_serde(&-0x0123_4567_89AB_CDEF_i64, &mut [0; 1024]); + test_serde_with_indices(&-0x0123_4567_89AB_CDEF_i64, &mut [0; 1024]); + test_deser::(&[Type::SignedInt.into(), 0x00]); + test_deser_with_indices::(&[Type::SignedInt.into(), 0x00]); + test_deser::(&[Type::SignedInt.into(), 0x01]); + test_deser_with_indices::(&[Type::SignedInt.into(), 0x01]); + test_deser::(&[Type::SignedInt.into(), 0xFE, 0x01]); + test_deser_with_indices::(&[Type::SignedInt.into(), 0xFE, 0x01]); + test_deser::(&[Type::SignedInt.into(), 0xFF, 0x01]); + test_deser_with_indices::(&[Type::SignedInt.into(), 0xFF, 0x01]); +} + +#[test] +fn test_floats() { + init_tracing(); + test_serde(&3.5_f32, &mut [0; 1024]); + test_serde_with_indices(&3.5_f32, &mut [0; 1024]); + test_serde(&3.5_f64, &mut [0; 1024]); + test_serde_with_indices(&3.5_f64, &mut [0; 1024]); + test_serde(&-3.5_f64, &mut [0; 1024]); + test_serde_with_indices(&-3.5_f64, &mut [0; 1024]); + test_deser::(&[Type::Float32.into(), 0x12, 0x34, 0x56, 0x78]); + test_deser_with_indices::(&[Type::Float32.into(), 0x12, 0x34, 0x56, 0x78]); + test_deser::(&[Type::Float64.into(), 1, 2, 3, 4, 5, 6, 7, 8]); + test_deser_with_indices::(&[Type::Float64.into(), 1, 2, 3, 4, 5, 6, 7, 8]); +} + +#[test] +fn test_bytes() { + init_tracing(); + test_serde(&Bytes::new(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), &mut [0; 1024]); + test_serde_with_indices(&Bytes::new(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), &mut [0; 1024]); + test_deser::<&Bytes>(&[Type::Bytes.into(), 5, 1, 2, 3, 4, 5]); + test_deser_with_indices::<&Bytes>(&[Type::Bytes.into(), 5, 1, 2, 3, 4, 5]); +} + +#[test] +fn test_string() { + init_tracing(); + test_serde(&"I was serialized and deserialized!", &mut [0; 1024]); + test_serde_with_indices(&"I was serialized and deserialized!", &mut [0; 1024]); + test_deser::<&str>(&[Type::String.into(), 5, b'H', b'e', b'l', b'l', b'o']); + test_deser_with_indices::<&str>(&[Type::String.into(), 5, b'H', b'e', b'l', b'l', b'o']); +} + +#[test] +fn test_char() { + init_tracing(); + test_serde(&'😻', &mut [0; 1024]); + test_serde_with_indices(&'😻', &mut [0; 1024]); + test_deser::(&[Type::String.into(), 1, b'x']); + test_deser_with_indices::(&[Type::String.into(), 1, b'x']); +} + +#[test] +fn test_sequence() { + init_tracing(); + test_serde(&[0; 0], &mut [0; 1024]); + test_serde_with_indices(&[0; 0], &mut [0; 1024]); + test_serde(&[true, false, true, false], &mut [0; 1024]); + test_serde_with_indices(&[true, false, true, false], &mut [0; 1024]); + test_deser::<[bool; 0]>(&[Type::SeqStart.into(), Type::SeqEnd.into()]); + test_deser::<[Option; 3]>(&[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ]); + test_deser_with_indices::<[bool; 0]>(&[Type::SeqStart.into(), Type::SeqEnd.into()]); + test_deser_with_indices::<[Option; 3]>(&[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ]); +} + +#[test] +fn test_tuple() { + init_tracing(); + test_serde(&(1,), &mut [0; 1024]); + test_serde_with_indices(&(1,), &mut [0; 1024]); + test_serde(&(1, 'h', 'i', 8), &mut [0; 1024]); + test_serde_with_indices(&(1, 'h', 'i', 8), &mut [0; 1024]); + test_deser::<(&str,)>(&[Type::SeqStart.into(), Type::String.into(), 0, Type::SeqEnd.into()]); + test_deser::<((), bool, Option, &str)>(&[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::String.into(), + 0, + Type::SeqEnd.into(), + ]); + test_deser_with_indices::<(&str,)>(&[ + Type::SeqStart.into(), + Type::String.into(), + 0, + Type::SeqEnd.into(), + ]); + test_deser_with_indices::<((), bool, Option, &str)>(&[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::String.into(), + 0, + Type::SeqEnd.into(), + ]); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_map() { + use ::alloc::collections::BTreeMap; + + init_tracing(); + + let mut map = BTreeMap::new(); + map.insert("null", 0); + map.insert("one", 1); + map.insert("two", 2); + map.insert("three", 3); + map.insert("four", 4); + test_serde(&map, &mut [0; 1024]); + test_serde_with_indices(&map, &mut [0; 1024]); + test_deser::>(&[Type::MapStart.into(), Type::MapEnd.into()]); + test_deser::>(&[ + Type::MapStart.into(), + Type::BooleanFalse.into(), + Type::String.into(), + 0, + Type::MapEnd.into(), + ]); + test_deser_with_indices::>(&[Type::MapStart.into(), Type::MapEnd.into()]); + test_deser_with_indices::>(&[ + Type::MapStart.into(), + Type::BooleanFalse.into(), + Type::String.into(), + 0, + Type::MapEnd.into(), + ]); + test_deser_with_indices::>(&[ + Type::MapStart.into(), + Type::String.into(), + 0, + Type::BooleanFalse.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_option() { + init_tracing(); + test_serde(&None::, &mut [0; 1024]); + test_serde_with_indices(&None::, &mut [0; 1024]); + test_serde(&Some(true), &mut [0; 1024]); + test_serde_with_indices(&Some(true), &mut [0; 1024]); + test_serde(&None::, &mut [0; 1024]); + test_serde_with_indices(&None::, &mut [0; 1024]); + test_serde(&Some('a'), &mut [0; 1024]); + test_serde_with_indices(&Some('a'), &mut [0; 1024]); + test_serde(&None::, &mut [0; 1024]); + test_serde_with_indices(&None::, &mut [0; 1024]); + test_serde(&Some(5), &mut [0; 1024]); + test_serde_with_indices(&Some(5), &mut [0; 1024]); + test_deser::>(&[Type::Null.into()]); + test_deser::>(&[Type::SignedInt.into(), 5]); + test_deser_with_indices::>(&[Type::Null.into()]); + test_deser_with_indices::>(&[Type::SignedInt.into(), 5]); +} + +#[test] +fn test_format_args() { + init_tracing(); + let mut buffer = [0; 1024]; + let bytes = crate::to_slice(&format_args!("XYZ {}", 5), &mut buffer).unwrap(); + assert_eq!(bytes, &[Type::String.into(), 5, b'X', b'Y', b'Z', b' ', b'5']); +} + + +#[test] +fn test_empty_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct EmptyStruct {} + + init_tracing(); + test_serde(&EmptyStruct {}, &mut [0; 1024]); + test_serde_with_indices(&EmptyStruct {}, &mut [0; 1024]); + test_deser::(&[Type::MapStart.into(), Type::MapEnd.into()]); + test_deser_with_indices::(&[Type::MapStart.into(), Type::MapEnd.into()]); +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Struct { + a: bool, + b: bool, + } + + init_tracing(); + test_serde(&Struct { a: false, b: true }, &mut [0; 1024]); + test_serde_with_indices(&Struct { a: false, b: true }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_newtype_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct NewtypeStruct(bool); + + init_tracing(); + test_serde(&NewtypeStruct(false), &mut [0; 1024]); + test_serde_with_indices(&NewtypeStruct(false), &mut [0; 1024]); + test_deser::(&[Type::BooleanTrue.into()]); + test_deser_with_indices::(&[Type::BooleanTrue.into()]); +} + +#[test] +fn test_tuple_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct TupleStruct(bool, bool); + + init_tracing(); + test_serde(&TupleStruct(false, true), &mut [0; 1024]); + test_serde_with_indices(&TupleStruct(false, true), &mut [0; 1024]); + test_deser::(&[ + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ]); +} + +#[test] +fn test_enums() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum EnumVariants { + Unit, + Newtype(bool), + Tuple(bool, bool), + Struct { a: bool, b: bool }, + } + + init_tracing(); + + test_serde(&EnumVariants::Unit, &mut [0; 1024]); + test_serde_with_indices(&EnumVariants::Unit, &mut [0; 1024]); + test_serde(&EnumVariants::Newtype(false), &mut [0; 1024]); + test_serde_with_indices(&EnumVariants::Newtype(false), &mut [0; 1024]); + test_serde(&EnumVariants::Tuple(false, true), &mut [0; 1024]); + test_serde_with_indices(&EnumVariants::Tuple(false, true), &mut [0; 1024]); + test_serde(&EnumVariants::Struct { a: false, b: true }, &mut [0; 1024]); + test_serde_with_indices(&EnumVariants::Struct { a: false, b: true }, &mut [0; 1024]); + test_serde( + &[ + EnumVariants::Unit, + EnumVariants::Newtype(true), + EnumVariants::Tuple(true, false), + EnumVariants::Struct { a: true, b: false }, + ], + &mut [0; 1024], + ); + test_serde_with_indices( + &[ + EnumVariants::Unit, + EnumVariants::Newtype(true), + EnumVariants::Tuple(true, false), + EnumVariants::Struct { a: true, b: false }, + ], + &mut [0; 1024], + ); + + test_deser::(&[Type::String.into(), 4, b'U', b'n', b'i', b't']); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 7, + b'N', + b'e', + b'w', + b't', + b'y', + b'p', + b'e', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 5, + b'T', + b'u', + b'p', + b'l', + b'e', + Type::SeqStart.into(), + Type::BooleanTrue.into(), + Type::BooleanFalse.into(), + Type::SeqEnd.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 2, + Type::SeqStart.into(), + Type::BooleanTrue.into(), + Type::BooleanFalse.into(), + Type::SeqEnd.into(), + Type::MapEnd.into(), + ]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 6, + b'S', + b't', + b'r', + b'u', + b'c', + b't', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 3, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_enums_with_discriminants() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum Enum { + VarA = 5, + VarB = 10, + } + + init_tracing(); + + test_serde(&Enum::VarA, &mut [0; 1024]); + test_serde(&Enum::VarB, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarA, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarB, &mut [0; 1024]); + + test_deser::(&[Type::String.into(), 4, b'V', b'a', b'r', b'A']); + test_deser::(&[Type::String.into(), 4, b'V', b'a', b'r', b'B']); + // Serde expect indices, not discriminants. + test_deser_with_indices::(&[Type::UnsignedInt.into(), 0]); + test_deser_with_indices::(&[Type::UnsignedInt.into(), 1]); +} + +#[test] +fn test_all_numbers() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct NumbersStruct { + u_8: u8, + u_16: u16, + u_32: u32, + u_64: u64, + u_128: u128, + u_size: usize, + i_8: i8, + i_16: i16, + i_32: i32, + i_64: i64, + i_128: i128, + i_size: isize, + f_32: f32, + f_64: f64, + } + + init_tracing(); + + test_serde( + &NumbersStruct { + u_8: 0, + u_16: 0, + u_32: 0, + u_64: 0, + u_128: 0, + u_size: 0, + i_8: 0, + i_16: 0, + i_32: 0, + i_64: 0, + i_128: 0, + i_size: 0, + f_32: 0.0, + f_64: 0.0, + }, + &mut [0; 1024], + ); + test_serde_with_indices( + &NumbersStruct { + u_8: 0, + u_16: 0, + u_32: 0, + u_64: 0, + u_128: 0, + u_size: 0, + i_8: 0, + i_16: 0, + i_32: 0, + i_64: 0, + i_128: 0, + i_size: 0, + f_32: 0.0, + f_64: 0.0, + }, + &mut [0; 1024], + ); + test_serde( + &NumbersStruct { + u_8: u8::MIN, + u_16: u16::MIN, + u_32: u32::MIN, + u_64: u64::MIN, + u_128: u128::MIN, + u_size: usize::MIN, + i_8: i8::MIN, + i_16: i16::MIN, + i_32: i32::MIN, + i_64: i64::MIN, + i_128: i128::MIN, + i_size: isize::MIN, + f_32: f32::MIN, + f_64: f64::MIN, + }, + &mut [0; 1024], + ); + test_serde_with_indices( + &NumbersStruct { + u_8: u8::MIN, + u_16: u16::MIN, + u_32: u32::MIN, + u_64: u64::MIN, + u_128: u128::MIN, + u_size: usize::MIN, + i_8: i8::MIN, + i_16: i16::MIN, + i_32: i32::MIN, + i_64: i64::MIN, + i_128: i128::MIN, + i_size: isize::MIN, + f_32: f32::MIN, + f_64: f64::MIN, + }, + &mut [0; 1024], + ); + test_serde( + &NumbersStruct { + u_8: u8::MAX, + u_16: u16::MAX, + u_32: u32::MAX, + u_64: u64::MAX, + u_128: u128::MAX, + u_size: usize::MAX, + i_8: i8::MAX, + i_16: i16::MAX, + i_32: i32::MAX, + i_64: i64::MAX, + i_128: i128::MAX, + i_size: isize::MAX, + f_32: f32::MAX, + f_64: f64::MAX, + }, + &mut [0; 1024], + ); + test_serde_with_indices( + &NumbersStruct { + u_8: u8::MAX, + u_16: u16::MAX, + u_32: u32::MAX, + u_64: u64::MAX, + u_128: u128::MAX, + u_size: usize::MAX, + i_8: i8::MAX, + i_16: i16::MAX, + i_32: i32::MAX, + i_64: i64::MAX, + i_128: i128::MAX, + i_size: isize::MAX, + f_32: f32::MAX, + f_64: f64::MAX, + }, + &mut [0; 1024], + ); + + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 3, + b'u', + b'_', + b'8', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 4, + b'u', + b'_', + b'1', + b'6', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 4, + b'u', + b'_', + b'3', + b'2', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 4, + b'u', + b'_', + b'6', + b'4', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 5, + b'u', + b'_', + b'1', + b'2', + b'8', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 6, + b'u', + b'_', + b's', + b'i', + b'z', + b'e', + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 3, + b'i', + b'_', + b'8', + Type::SignedInt.into(), + 0, + Type::String.into(), + 4, + b'i', + b'_', + b'1', + b'6', + Type::SignedInt.into(), + 0, + Type::String.into(), + 4, + b'i', + b'_', + b'3', + b'2', + Type::SignedInt.into(), + 0, + Type::String.into(), + 4, + b'i', + b'_', + b'6', + b'4', + Type::SignedInt.into(), + 0, + Type::String.into(), + 5, + b'i', + b'_', + b'1', + b'2', + b'8', + Type::SignedInt.into(), + 0, + Type::String.into(), + 6, + b'i', + b'_', + b's', + b'i', + b'z', + b'e', + Type::SignedInt.into(), + 0, + Type::String.into(), + 4, + b'f', + b'_', + b'3', + b'2', + Type::Float32.into(), + 1, + 2, + 3, + 4, + Type::String.into(), + 4, + b'f', + b'_', + b'6', + b'4', + Type::Float64.into(), + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 1, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 2, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 3, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 4, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 5, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 6, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 7, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 8, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 9, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 10, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 11, + Type::SignedInt.into(), + 0, + Type::UnsignedInt.into(), + 12, + Type::Float32.into(), + 1, + 2, + 3, + 4, + Type::UnsignedInt.into(), + 13, + Type::Float64.into(), + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_sequence_nested() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner { + a: bool, + b: bool, + } + + init_tracing(); + test_serde(&[Inner { a: false, b: true }, Inner { a: true, b: false }], &mut [0; 1024]); + test_serde_with_indices( + &[Inner { a: false, b: true }, Inner { a: true, b: false }], + &mut [0; 1024], + ); + test_serde(&[Inner { a: false, b: true }], &mut [0; 1024]); + test_serde_with_indices(&[Inner { a: false, b: true }], &mut [0; 1024]); + test_deser::<[Inner; 0]>(&[Type::SeqStart.into(), Type::SeqEnd.into()]); + test_deser_with_indices::<[Inner; 0]>(&[Type::SeqStart.into(), Type::SeqEnd.into()]); + test_deser::<[Inner; 1]>(&[ + Type::SeqStart.into(), + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::SeqEnd.into(), + ]); + test_deser_with_indices::<[Inner; 1]>(&[ + Type::SeqStart.into(), + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::SeqEnd.into(), + ]); +} + +#[test] +fn test_struct_nested() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner { + a: bool, + b: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Outer { + a: bool, + b: Inner, + c: Inner, + } + + init_tracing(); + test_serde( + &Outer { a: false, b: Inner { a: true, b: true }, c: Inner { a: false, b: false } }, + &mut [0; 1024], + ); + test_serde_with_indices( + &Outer { a: false, b: Inner { a: true, b: true }, c: Inner { a: false, b: false } }, + &mut [0; 1024], + ); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::String.into(), + 1, + b'b', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::String.into(), + 1, + b'c', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanTrue.into(), + Type::UnsignedInt.into(), + 1, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::UnsignedInt.into(), + 2, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); +} diff --git a/src/tests/features.rs b/src/tests/features.rs new file mode 100644 index 0000000..bbf70ff --- /dev/null +++ b/src/tests/features.rs @@ -0,0 +1,123 @@ +//! Tests for crate specific format features, e.g. things are borrowed zero-copy. + +use ::core::num::NonZeroUsize; +use ::serde_bytes::Bytes; + +use super::*; +use crate::{format::Type, Error}; + +#[test] +fn test_string_is_bytes() { + init_tracing(); + let mut buffer = [0; 1024]; + let value = "Hello, my name is bumble bee, bumble bee!"; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: &Bytes = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, value.as_bytes()); +} + +#[test] +fn test_bytes_is_byte_sequence() { + init_tracing(); + let mut buffer = [0; 1024]; + let value = Bytes::new(&[1, 2, 3]); + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: (u8, u8, u8) = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, (1, 2, 3)); +} + +#[test] +fn test_string_is_char_sequence() { + init_tracing(); + let mut buffer = [0; 1024]; + let value = "äü😻"; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: (char, char, char) = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, ('ä', 'ü', '😻')); +} + +#[test] +fn test_borrowing() { + let data = [Type::String.into(), 3, b's', b'h', b'y']; + let parsed = crate::from_slice::<&str>(&data).unwrap(); + assert_eq!(parsed, "shy"); + let data = [Type::Bytes.into(), 3, 1, 1, 1]; + let parsed = crate::from_slice::<&[u8]>(&data).unwrap(); + assert_eq!(parsed, &[1, 1, 1]); +} + +#[test] +fn test_deser_calls_borrowed() { + struct Test; + struct Visitor; + impl<'a, 'de> ::serde::de::Visitor<'de> for &'a mut Visitor { + type Value = Test; + + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(formatter, "a borrowed value") + } + + fn visit_borrowed_bytes(self, _v: &'de [u8]) -> Result + where + E: serde::de::Error, + { + Ok(Test) + } + + fn visit_borrowed_str(self, _v: &'de str) -> Result + where + E: serde::de::Error, + { + Ok(Test) + } + + // The other methods default to erroring. + } + impl<'de> ::serde::de::Deserialize<'de> for Test { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(&mut Visitor) + } + } + + let data = [Type::String.into(), 3, b's', b'h', b'y']; + crate::from_slice::(&data).unwrap(); + + let data = [Type::Bytes.into(), 3, b's', b'h', b'y']; + crate::from_slice::(&data).unwrap(); +} + +#[test] +fn test_excess_data() { + let config = Config { error_on_excess_data: true, ..Default::default() }; + let data = [Type::String.into(), 1, b'a', 0]; + let result = crate::from_slice_with_config::<&str>(&data, config); + assert!(matches!(result, Err(Error::ExcessData))); +} + +#[test] +fn test_max_size() { + let config = Config { max_size: Some(NonZeroUsize::new(5).unwrap()), ..Default::default() }; + let data = [Type::Bytes.into(), 4, 1, 2, 3, 4]; + let result = crate::from_slice_with_config::<&Bytes>(&data, config); + assert!(matches!(result, Err(Error::LimitReached))); + + let config = Config { max_size: Some(NonZeroUsize::new(5).unwrap()), ..Default::default() }; + let data = [Type::Bytes.into(), 3, 1, 2, 3]; + let result = crate::from_slice_with_config::<&Bytes>(&data, config); + assert!(result.is_ok()); + + let mut buffer = [0; 1024]; + + let config = Config { max_size: Some(NonZeroUsize::new(5).unwrap()), ..Default::default() }; + let data = Bytes::new(&[1, 2, 3, 4]); + let result = crate::to_slice_with_config(&data, &mut buffer, config); + assert!(matches!(result, Err(Error::LimitReached))); + + let config = Config { max_size: Some(NonZeroUsize::new(5).unwrap()), ..Default::default() }; + let data = Bytes::new(&[1, 2, 3]); + let result = crate::to_slice_with_config(&data, &mut buffer, config); + assert!(result.is_ok()); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..e75b982 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,86 @@ +//! General, basic serialization and deserialization tests. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::print_stdout, reason = "Tests")] + +mod basic_types; +mod features; +mod serde_features; +mod special_handling; +mod versioning; + +use ::core::fmt::Debug; +use ::serde::{Deserialize, Serialize}; + +use crate::Config; + +#[cfg(all(feature = "std", feature = "tracing"))] +pub fn init_tracing() { + static INITIALIZED: ::std::sync::OnceLock<()> = ::std::sync::OnceLock::new(); + + INITIALIZED.get_or_init(|| { + tracing_subscriber::fmt() + .with_test_writer() + .with_max_level(::tracing::Level::TRACE) + .pretty() + .with_span_events(::tracing_subscriber::fmt::format::FmtSpan::ACTIVE) + .init(); + }); +} +#[cfg(not(all(feature = "std", feature = "tracing")))] +#[allow(clippy::missing_const_for_fn, reason = "Different feature sets")] +pub fn init_tracing() { + #[cfg(feature = "std")] + println!("To see logs, run tests with the `std` and `tracing` feature enabled"); +} + + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(buffer)))] +fn test_serde<'de, T>(value: &T, buffer: &'de mut [u8]) +where + T: Serialize + Deserialize<'de> + PartialEq + Debug, +{ + let bytes = crate::to_slice(&value, buffer).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Byte representation: {bytes:?}"); + let deserialized: T = crate::from_slice(bytes).unwrap(); + assert_eq!(deserialized, *value); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(buffer)))] +fn test_serde_with_indices<'de, T>(value: &T, buffer: &'de mut [u8]) +where + T: Serialize + Deserialize<'de> + PartialEq + Debug, +{ + let config = Config { use_indices: true, ..Default::default() }; + let bytes = crate::to_slice_with_config(&value, buffer, config).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Byte representation: {bytes:?}"); + let deserialized: T = crate::from_slice_with_config(bytes, config).unwrap(); + assert_eq!(deserialized, *value); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument())] +fn test_deser<'de, T>(bytes: &'de [u8]) +where + T: Deserialize<'de> + Serialize + Debug, +{ + let deserialized: T = crate::from_slice(bytes).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Deserialized value: {deserialized:?}"); + let mut buffer = [0; 1024]; + let serialized = crate::to_slice(&deserialized, &mut buffer).unwrap(); + assert_eq!(serialized, bytes); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument())] +fn test_deser_with_indices<'de, T>(bytes: &'de [u8]) +where + T: Deserialize<'de> + Serialize + Debug, +{ + let config = Config { use_indices: true, ..Default::default() }; + let deserialized: T = crate::from_slice_with_config(bytes, config).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Deserialized value: {deserialized:?}"); + let mut buffer = [0; 1024]; + let serialized = crate::to_slice_with_config(&deserialized, &mut buffer, config).unwrap(); + assert_eq!(serialized, bytes); +} diff --git a/src/tests/serde_features.rs b/src/tests/serde_features.rs new file mode 100644 index 0000000..3f730c1 --- /dev/null +++ b/src/tests/serde_features.rs @@ -0,0 +1,620 @@ +//! Tests for the serde specifics and features, e.g. #[serde(rename = "X", alias = "Y")]. + +use ::serde_bytes::Bytes; + +use super::*; +use crate::{format::Type, Error}; + +#[test] +fn test_rename_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(rename = "V1", rename_all = "PascalCase")] + struct Struct { + first_field: bool, + #[serde(rename = "Extension")] + second_field: bool, + } + + init_tracing(); + test_serde(&Struct { first_field: true, second_field: true }, &mut [0; 1024]); + test_serde_with_indices(&Struct { first_field: true, second_field: true }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 10, + b'F', + b'i', + b'r', + b's', + b't', + b'F', + b'i', + b'e', + b'l', + b'd', + Type::BooleanTrue.into(), + Type::String.into(), + 9, + b'E', + b'x', + b't', + b'e', + b'n', + b's', + b'i', + b'o', + b'n', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_rename_enum() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(rename = "V1", rename_all = "kebab-case", rename_all_fields = "PascalCase")] + enum Enum { + VarA { + first_field: bool, + #[serde(rename = "Extension")] + second_field: bool, + }, + } + + init_tracing(); + test_serde(&Enum::VarA { first_field: true, second_field: true }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarA { first_field: true, second_field: true }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 5, + b'v', + b'a', + b'r', + b'-', + b'a', + Type::MapStart.into(), + Type::String.into(), + 10, + b'F', + b'i', + b'r', + b's', + b't', + b'F', + b'i', + b'e', + b'l', + b'd', + Type::BooleanTrue.into(), + Type::String.into(), + 9, + b'E', + b'x', + b't', + b'e', + b'n', + b's', + b'i', + b'o', + b'n', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_alias() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Struct { + #[serde(alias = "a")] + first_field: bool, + } + + init_tracing(); + let parsed: Struct = crate::from_slice(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]) + .unwrap(); + assert_eq!(parsed, Struct { first_field: true }); +} + +#[test] +fn test_deny_unknown_fields() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(deny_unknown_fields)] + struct Deny { + a: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Accept { + a: bool, + } + + init_tracing(); + let config = Config { use_indices: true, ..Default::default() }; + + let result = crate::from_slice::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); + #[cfg(feature = "alloc")] + assert!(matches!(result, Err(Error::Message(_)))); + #[cfg(not(feature = "alloc"))] + assert!(matches!(result, Err(Error::Custom))); + + let result = crate::from_slice_with_config::( + &[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanTrue.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ], + config, + ); + #[cfg(feature = "alloc")] + assert!(matches!(result, Err(Error::Message(_)))); + #[cfg(not(feature = "alloc"))] + assert!(matches!(result, Err(Error::Custom))); + + let parsed: Accept = crate::from_slice(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]) + .unwrap(); + assert_eq!(parsed, Accept { a: true }); + + let parsed: Accept = crate::from_slice_with_config( + &[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanTrue.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ], + config, + ) + .unwrap(); + assert_eq!(parsed, Accept { a: true }); +} + +#[test] +fn test_tagged_enum() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "type")] + enum Enum { + VarA { a: bool }, + } + + init_tracing(); + test_serde(&Enum::VarA { a: true }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 4, + b't', + b'y', + b'p', + b'e', + Type::String.into(), + 4, + b'V', + b'a', + b'r', + b'A', + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); + + // Using `use_indices` with internally-tagged enums is not supported, it calls serialize_struct, + // but deserialize_any and does not recognize index keys. +} + +#[test] +fn test_tagged_enum_with_content() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "t", content = "c")] + enum Enum { + VarA { a: bool }, + } + + init_tracing(); + test_serde(&Enum::VarA { a: true }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarA { a: true }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b't', + Type::String.into(), + 4, + b'V', + b'a', + b'r', + b'A', + Type::String.into(), + 1, + b'c', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 1, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); +} + + +#[test] +fn test_untagged_enum() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum Enum { + VarA { a: bool }, + VarB { b: u8 }, + VarC { a: u8 }, + } + + init_tracing(); + test_serde(&Enum::VarA { a: true }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarA { a: true }, &mut [0; 1024]); + test_serde(&Enum::VarB { b: 5 }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarB { b: 5 }, &mut [0; 1024]); + test_serde(&Enum::VarC { a: 5 }, &mut [0; 1024]); + // With indices, it is impossible to recognize `VarC`. + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'b', + Type::UnsignedInt.into(), + 1, + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 1, + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_enum_single_untagged() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum Enum { + VarA { + a: bool, + }, + #[serde(untagged)] + VarB { + b: u8, + }, + } + + init_tracing(); + test_serde(&Enum::VarA { a: true }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarA { a: true }, &mut [0; 1024]); + test_serde(&Enum::VarB { b: 5 }, &mut [0; 1024]); + test_serde_with_indices(&Enum::VarB { b: 5 }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'b', + Type::UnsignedInt.into(), + 1, + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::UnsignedInt.into(), + 1, + Type::MapEnd.into(), + ]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 4, + b'V', + b'a', + b'r', + b'A', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_struct_default() { + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] + #[serde(default)] + struct OwnDefault { + first_field: u8, + second_field: u8, + } + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] + #[serde(default = "my_default")] + struct SeparateDefault { + first_field: u8, + second_field: u8, + } + const fn my_default() -> SeparateDefault { + SeparateDefault { first_field: 5, second_field: 5 } + } + + init_tracing(); + let config = Config { use_indices: true, ..Default::default() }; + + let parsed = + crate::from_slice::(&[Type::MapStart.into(), Type::MapEnd.into()]).unwrap(); + assert_eq!(parsed.first_field, 0); + assert_eq!(parsed.second_field, 0); + let parsed = crate::from_slice_with_config::( + &[Type::MapStart.into(), Type::MapEnd.into()], + config, + ) + .unwrap(); + assert_eq!(parsed.first_field, 0); + assert_eq!(parsed.second_field, 0); + + let parsed = + crate::from_slice::(&[Type::MapStart.into(), Type::MapEnd.into()]) + .unwrap(); + assert_eq!(parsed.first_field, 5); + assert_eq!(parsed.second_field, 5); + let parsed = crate::from_slice_with_config::( + &[Type::MapStart.into(), Type::MapEnd.into()], + config, + ) + .unwrap(); + assert_eq!(parsed.first_field, 5); + assert_eq!(parsed.second_field, 5); +} + +#[test] +fn test_transparent() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(transparent)] + struct Struct { + content: bool, + } + + init_tracing(); + test_serde(&Struct { content: true }, &mut [0; 1024]); + test_serde_with_indices(&Struct { content: true }, &mut [0; 1024]); + test_deser::(&[Type::BooleanTrue.into()]); + test_deser_with_indices::(&[Type::BooleanTrue.into()]); +} + +#[test] +fn test_from_into() { + #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] + #[serde(from = "bool", into = "bool")] + struct Struct { + boolean: u8, + } + impl From for Struct { + fn from(value: bool) -> Self { + Struct { boolean: if value { 1 } else { 0 } } + } + } + impl From for bool { + fn from(value: Struct) -> Self { + value.boolean != 0 + } + } + + init_tracing(); + test_serde(&Struct { boolean: 1 }, &mut [0; 1024]); + test_serde_with_indices(&Struct { boolean: 1 }, &mut [0; 1024]); + test_deser::(&[Type::BooleanFalse.into()]); + test_deser_with_indices::(&[Type::BooleanFalse.into()]); +} + +#[test] +fn test_enum_other() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "t")] + enum Enum { + VarA, + #[serde(other)] + Default, + } + + init_tracing(); + + test_serde(&Enum::VarA, &mut [0; 1024]); + test_serde(&Enum::Default, &mut [0; 1024]); + + let parsed = crate::from_slice::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b't', + Type::String.into(), + 1, + 0, + Type::MapEnd.into(), + ]) + .unwrap(); + assert_eq!(parsed, Enum::Default); + + // Using `use_indices` with internally-tagged enums is not supported, it calls serialize_struct, + // but deserialize_any and does not recognize index keys. +} + +#[test] +fn test_flatten() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner { + b: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Outer { + a: bool, + #[serde(flatten)] + ext: Inner, + } + + init_tracing(); + test_serde(&Outer { a: true, ext: Inner { b: true } }, &mut [0; 1024]); + test_serde_with_indices(&Outer { a: true, ext: Inner { b: true } }, &mut [0; 1024]); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); + // Flatten does not support indices, it is always a string map. + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ]); +} + +#[test] +fn test_borrow() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner<'a> { + s: &'a str, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Outer<'a> { + #[serde(borrow)] + i: Inner<'a>, + b: &'a Bytes, + } + + init_tracing(); + test_serde(&Outer { b: Bytes::new(&[1, 2]), i: Inner { s: "hi" } }, &mut [0; 1024]); + test_serde_with_indices( + &Outer { b: Bytes::new(&[1, 2]), i: Inner { s: "hi" } }, + &mut [0; 1024], + ); + test_deser::(&[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'i', + Type::MapStart.into(), + Type::String.into(), + 1, + b's', + Type::String.into(), + 2, + b'h', + b'i', + Type::MapEnd.into(), + Type::String.into(), + 1, + b'b', + Type::Bytes.into(), + 2, + 1, + 2, + Type::MapEnd.into(), + ]); + test_deser_with_indices::(&[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::String.into(), + 2, + b'h', + b'i', + Type::MapEnd.into(), + Type::UnsignedInt.into(), + 1, + Type::Bytes.into(), + 2, + 1, + 2, + Type::MapEnd.into(), + ]); +} diff --git a/src/tests/special_handling.rs b/src/tests/special_handling.rs new file mode 100644 index 0000000..8348b5d --- /dev/null +++ b/src/tests/special_handling.rs @@ -0,0 +1,56 @@ +//! Tests for special cases in serialization and deserialization. + +use ::serde_bytes::Bytes; + +use super::*; +use crate::{format::Type, Error}; + +#[test] +fn test_type_mismatch() { + init_tracing(); + let mut buffer = [0; 1024]; + + let value = true; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::<()>(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::BooleanTrue, &[Type::Null])))); + + let value = true; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::BooleanTrue, &[Type::SignedInt])))); + + let value = 0.0_f32; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::Float32, &[Type::SignedInt])))); + + let value = 0_usize; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::UnsignedInt, _)))); + + let value = 0_usize; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::UnsignedInt, &[Type::SignedInt])))); + + let value = Bytes::new(&[1, 2, 3, 4]); + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let result = crate::from_slice::<&str>(bytes); + assert!(matches!(result, Err(Error::WrongType(Type::Bytes, &[Type::String])))); +} + +#[test] +fn test_unexpected_eof() { + let data = [Type::String.into(), 2, b'a']; + let result = crate::from_slice::<&str>(&data); + assert!(matches!(result, Err(Error::UnexpectedEnd))); +} + +#[test] +fn test_output_too_small() { + let mut buffer = [0; 5]; + let result = crate::to_slice(&"hallo", &mut buffer); + assert!(matches!(result, Err(Error::BufferTooSmall))); +} diff --git a/src/tests/versioning.rs b/src/tests/versioning.rs new file mode 100644 index 0000000..03b7cdc --- /dev/null +++ b/src/tests/versioning.rs @@ -0,0 +1,163 @@ +//! Test whether it is possible to make structs/enums forward/backward compatible. + +use super::*; + +#[test] +fn test_struct_field_added() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V1 { + a: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V2 { + #[serde(default)] + b: bool, + a: bool, + } + + init_tracing(); + let mut buffer = [0; 1024]; + let value = V1 { a: true }; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed.a, value.a); +} + +#[test] +fn test_struct_field_added_with_indices() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V1 { + a: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V2 { + a: bool, + #[serde(default)] + b: bool, + } + + init_tracing(); + let config = Config { use_indices: true, ..Default::default() }; + let mut buffer = [0; 1024]; + let value = V1 { a: true }; + let bytes = crate::to_slice_with_config(&value, &mut buffer, config).unwrap(); + let parsed: V2 = crate::from_slice_with_config(bytes, config).unwrap(); + assert_eq!(parsed.a, value.a); +} + +#[test] +fn test_struct_fields_reordered() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V1 { + a: bool, + b: bool, + c: bool, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct V2 { + b: bool, + c: bool, + a: bool, + } + + init_tracing(); + let mut buffer = [0; 1024]; + let value = V1 { a: true, b: false, c: true }; + + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed.a, value.a); + assert_eq!(parsed.b, value.b); + assert_eq!(parsed.c, value.c); +} + +#[test] +fn test_enum_variant_added() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V1 { + Some(bool), + Multiple(bool, bool), + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V2 { + None, + Some(bool), + Multiple(bool, bool), + } + + init_tracing(); + let mut buffer = [0; 1024]; + + let value = V1::Some(true); + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, V2::Some(true)); + + let value = V1::Multiple(false, true); + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, V2::Multiple(false, true)); +} + +#[test] +fn test_enum_variant_added_with_indices() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V1 { + None, + Some(bool), + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V2 { + None, + Some(bool), + Multiple(bool, bool), + } + + init_tracing(); + let config = Config { use_indices: true, ..Default::default() }; + let mut buffer = [0; 1024]; + + let value = V1::Some(true); + let bytes = crate::to_slice_with_config(&value, &mut buffer, config).unwrap(); + let parsed: V2 = crate::from_slice_with_config(bytes, config).unwrap(); + assert_eq!(parsed, V2::Some(true)); + + let value = V1::None; + let bytes = crate::to_slice_with_config(&value, &mut buffer, config).unwrap(); + let parsed: V2 = crate::from_slice_with_config(bytes, config).unwrap(); + assert_eq!(parsed, V2::None); +} + +#[test] +fn test_enum_variants_reordered() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V1 { + X, + Y, + Z, + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum V2 { + Z, + X, + Y, + } + + init_tracing(); + let mut buffer = [0; 1024]; + + let value = V1::X; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, V2::X); + + let value = V1::Y; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, V2::Y); + + let value = V1::Z; + let bytes = crate::to_slice(&value, &mut buffer).unwrap(); + let parsed: V2 = crate::from_slice(bytes).unwrap(); + assert_eq!(parsed, V2::Z); +} diff --git a/src/value/de.rs b/src/value/de.rs new file mode 100644 index 0000000..2aa8dd5 --- /dev/null +++ b/src/value/de.rs @@ -0,0 +1,649 @@ +//! Deserialization from [Value] to any type. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +use ::serde::de::{Error, IntoDeserializer, Unexpected}; + +use super::*; + +/// Deserializer to deserialize a [Value] into any type. +#[derive(Debug)] +pub struct ValueDeserializer<'de>(Value<'de>); + +impl<'de> ValueDeserializer<'de> { + /// Create a new deserializer from the given value. + #[must_use] + pub const fn new(value: Value<'de>) -> Self { + Self(value) + } + + /// Deserialize the value. + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn deserialize(self, visitor: V) -> Result + where + V: ::serde::de::Visitor<'de>, + { + match self.0 { + Value::Null => visitor.visit_none(), + Value::Bool(b) => visitor.visit_bool(b), + Value::Integer(int) => visit_integer(int, visitor), + Value::Float(Float::F32(float)) => visitor.visit_f32(float), + Value::Float(Float::F64(float)) => visitor.visit_f64(float), + Value::Bytes(Cow::Borrowed(bytes)) => visitor.visit_borrowed_bytes(bytes), + Value::Bytes(Cow::Owned(bytes)) => visitor.visit_byte_buf(bytes), + Value::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Value::String(Cow::Owned(s)) => visitor.visit_string(s), + Value::Array(arr) => visitor.visit_seq(ValueSeqDeserializer(arr)), + Value::Map(map) => visitor.visit_map(ValueMapDeserializer(map)), + } + } +} + +impl<'de> ::serde::de::Deserializer<'de> for ValueDeserializer<'de> { + type Error = crate::Error; + + #[inline] + fn is_human_readable(&self) -> bool { + true + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + Self::deserialize(self, visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_bool(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Bool(value) => visitor.visit_bool(value), + other => Err(Error::invalid_type(Unexpected::from(&other), &"bool")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_i8(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"i8")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_i16(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"i16")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_i32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"i32")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_i64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"i64")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_u8(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"u8")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_u16(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"u16")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_u32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"u32")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_u64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"u64")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_i128(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"i128")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_u128(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(int) => visit_integer(int, visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"u128")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_f32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Float(Float::F32(float)) => visitor.visit_f32(float), + Value::Float(Float::F64(float)) => visitor.visit_f64(float), + other => Err(Error::invalid_type(Unexpected::from(&other), &"float")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_f64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Float(Float::F32(float)) => visitor.visit_f32(float), + Value::Float(Float::F64(float)) => visitor.visit_f64(float), + other => Err(Error::invalid_type(Unexpected::from(&other), &"float")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_char(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Value::String(Cow::Owned(s)) => visitor.visit_string(s), + other => Err(Error::invalid_type(Unexpected::from(&other), &"char")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_str(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Value::String(Cow::Owned(s)) => visitor.visit_string(s), + other => Err(Error::invalid_type(Unexpected::from(&other), &"string")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_string(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Value::String(Cow::Owned(s)) => visitor.visit_string(s), + other => Err(Error::invalid_type(Unexpected::from(&other), &"string")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_bytes(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Bytes(Cow::Borrowed(bytes)) => visitor.visit_borrowed_bytes(bytes), + Value::Bytes(Cow::Owned(bytes)) => visitor.visit_byte_buf(bytes), + other => Err(Error::invalid_type(Unexpected::from(&other), &"bytes")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Bytes(Cow::Borrowed(bytes)) => visitor.visit_borrowed_bytes(bytes), + Value::Bytes(Cow::Owned(bytes)) => visitor.visit_byte_buf(bytes), + other => Err(Error::invalid_type(Unexpected::from(&other), &"bytes")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if matches!(&self.0, Value::Null) { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_unit(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Null => visitor.visit_unit(), + other => Err(Error::invalid_type(Unexpected::from(&other), &"unit")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Null => visitor.visit_unit(), + other => Err(Error::invalid_type(Unexpected::from(&other), &"unit")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_seq(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Array(arr) => visitor.visit_seq(ValueSeqDeserializer(arr)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"sequence")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Array(arr) => visitor.visit_seq(ValueSeqDeserializer(arr)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"tuple")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Array(arr) => visitor.visit_seq(ValueSeqDeserializer(arr)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"tuple struct")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_map(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Map(map) => visitor.visit_map(ValueMapDeserializer(map)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"map")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Map(map) => visitor.visit_map(ValueMapDeserializer(map)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"map")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(Integer::Unsigned(int)) => { + visitor.visit_enum((int as u32).into_deserializer()) + } + Value::String(s) => visitor.visit_enum(s.as_ref().into_deserializer()), + Value::Map(map) => visitor.visit_enum(ValueEnumDeserializer(map)), + other => Err(Error::invalid_type(Unexpected::from(&other), &"enum")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_identifier(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Integer(Integer::Unsigned(_)) => self.deserialize_u32(visitor), + Value::String(_) => self.deserialize_str(visitor), + other => Err(Error::invalid_type(Unexpected::from(&other), &"identifier")), + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } +} + +/// Enum deserializer. +#[derive(Debug)] +struct ValueEnumDeserializer<'de>(VecDeque<(Value<'de>, Value<'de>)>); + +impl<'de> ::serde::de::EnumAccess<'de> for ValueEnumDeserializer<'de> { + type Error = crate::Error; + type Variant = ValueDeserializer<'de>; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: serde::de::DeserializeSeed<'de>, + { + if self.0.len() != 1 { + return Err(Error::invalid_length(1, &"exactly one key-value-pair")); + } + + #[expect(clippy::unwrap_used, reason = "Was just checked")] + let (key, value) = self.0.pop_front().unwrap(); + let res = seed.deserialize(ValueDeserializer(key))?; + Ok((res, ValueDeserializer(value))) + } +} + +impl<'de> ::serde::de::VariantAccess<'de> for ValueDeserializer<'de> { + type Error = crate::Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn unit_variant(self) -> Result<(), Self::Error> { + Err(Error::invalid_type(Unexpected::from(&self.0), &"unit variant")) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn newtype_variant_seed(self, seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + ::serde::de::Deserializer::deserialize_seq(self, visitor) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, visitor)))] + fn struct_variant( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + ::serde::de::Deserializer::deserialize_map(self, visitor) + } +} + +/// Sequence deserializer. +#[derive(Debug)] +struct ValueSeqDeserializer<'de>(VecDeque>); + +impl<'de> ::serde::de::SeqAccess<'de> for ValueSeqDeserializer<'de> { + type Error = crate::Error; + + #[inline] + fn size_hint(&self) -> Option { + Some(self.0.len()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + if let Some(value) = self.0.pop_front() { + seed.deserialize(ValueDeserializer(value)).map(Some) + } else { + Ok(None) + } + } +} + +/// Map deserializer. +#[derive(Debug)] +struct ValueMapDeserializer<'de>(VecDeque<(Value<'de>, Value<'de>)>); + +impl<'de> ::serde::de::MapAccess<'de> for ValueMapDeserializer<'de> { + type Error = crate::Error; + + #[inline] + fn size_hint(&self) -> Option { + Some(self.0.len()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + { + if let Some((key, _value)) = self.0.front_mut() { + let value = ::core::mem::replace(key, Value::Null); + Ok(Some(seed.deserialize(ValueDeserializer(value))?)) + } else { + Ok(None) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + if let Some((_key, value)) = self.0.pop_front() { + Ok(seed.deserialize(ValueDeserializer(value))?) + } else { + Err(Error::custom("next_value_seed called without next_key_seed")) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + #[allow(clippy::type_complexity, reason = "Tracing makes this trigger, also it's serde trait")] + fn next_entry_seed( + &mut self, + kseed: K, + vseed: V, + ) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + V: serde::de::DeserializeSeed<'de>, + { + if let Some((key, value)) = self.0.pop_front() { + let key = kseed.deserialize(ValueDeserializer(key))?; + let value = vseed.deserialize(ValueDeserializer(value))?; + Ok(Some((key, value))) + } else { + Ok(None) + } + } +} + +/// Visit the integer, depending on its value / size. +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(visitor)))] +fn visit_integer<'de, V>(int: Integer, visitor: V) -> Result +where + V: ::serde::de::Visitor<'de>, +{ + #[allow(clippy::cast_lossless, reason = "We won't change it")] + match int { + Integer::Unsigned(int) if int <= u8::MAX as u128 => visitor.visit_u8(int as u8), + Integer::Unsigned(int) if int <= u16::MAX as u128 => visitor.visit_u16(int as u16), + Integer::Unsigned(int) if int <= u32::MAX as u128 => visitor.visit_u32(int as u32), + Integer::Unsigned(int) if int <= u64::MAX as u128 => visitor.visit_u64(int as u64), + Integer::Unsigned(int) => visitor.visit_u128(int), + Integer::Signed(int) if (i8::MIN as i128 ..= i8::MAX as i128).contains(&int) => { + visitor.visit_i8(int as i8) + } + Integer::Signed(int) if (i16::MIN as i128 ..= i16::MAX as i128).contains(&int) => { + visitor.visit_i16(int as i16) + } + Integer::Signed(int) if (i32::MIN as i128 ..= i32::MAX as i128).contains(&int) => { + visitor.visit_i32(int as i32) + } + Integer::Signed(int) if (i64::MIN as i128 ..= i64::MAX as i128).contains(&int) => { + visitor.visit_i64(int as i64) + } + Integer::Signed(int) => visitor.visit_i128(int), + } +} + +impl<'a, 'de> From<&'a Value<'de>> for Unexpected<'a> { + fn from(value: &'a Value<'de>) -> Self { + match value { + Value::Null => Unexpected::Unit, + Value::Bool(b) => Unexpected::Bool(*b), + Value::Integer(Integer::Unsigned(int)) => Unexpected::Unsigned(*int as u64), + Value::Integer(Integer::Signed(int)) => Unexpected::Signed(*int as i64), + Value::Float(Float::F32(float)) => Unexpected::Float(f64::from(*float)), + Value::Float(Float::F64(float)) => Unexpected::Float(*float), + Value::Bytes(bytes) => Unexpected::Bytes(bytes), + Value::String(s) => Unexpected::Str(s), + Value::Array(_arr) => Unexpected::Seq, + Value::Map(_map) => Unexpected::Map, + } + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..05c1b94 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,920 @@ +//! Generic value that can contain any value in our data format. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +mod de; +mod ser; + +use ::alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::VecDeque, + string::String, + vec::Vec, +}; +use ::core::{ + fmt::Write, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; +use ::serde::{Deserialize, Serialize}; + +use crate::{Config, Result}; + +/// Serialize a type to the generic [Value] type using the given configuration. +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(value)))] +pub fn to_value_with_config(value: &T, config: Config) -> Result> +where + T: Serialize, +{ + let ser = ser::ValueSerializer::new(config.use_indices); + value.serialize(ser) +} + +/// Serialize a type to the generic [Value] type. +pub fn to_value(value: &T) -> Result> +where + T: Serialize, +{ + to_value_with_config(value, Config::default()) +} + +/// Deserialize a type from a generic [Value] using the given configuration. +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(value)))] +pub fn from_value_with_config<'de, T>(value: Value<'de>, _config: Config) -> Result +where + T: Deserialize<'de>, +{ + let de = de::ValueDeserializer::new(value); + T::deserialize(de) +} + +/// Deserialize a type from a generic [Value]. +pub fn from_value<'de, T>(value: Value<'de>) -> Result +where + T: Deserialize<'de>, +{ + from_value_with_config(value, Config::default()) +} + +/// Generic value that can contain any value in our data format. It can be deserialized and +/// serialized to be exactly as when serializing or deserializing with the given type. +/// +/// Note: [Clone]ing this value will not borrow from owned values. For that, you need to call +/// [Value::borrow_clone]. +#[derive(Debug, Clone, Default)] +pub enum Value<'a> { + /// Null / None / Unit type. + #[default] + Null, + /// Bool value. + Bool(bool), + /// Integer value. + Integer(Integer), + /// Float value. + Float(Float), + /// Bytes value. + Bytes(Cow<'a, [u8]>), + /// String value. + String(Cow<'a, str>), + /// Sequence value. + Array(VecDeque), + /// Map value (ordered). + Map(VecDeque<(Self, Self)>), +} + +/// Wrapper for an owned value, i.e. `Value<'static>`. +#[derive(Debug, Clone, Default)] +pub struct OwnedValue(Value<'static>); + +/// The unsigned/signed integer value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Integer { + /// Unsigned integer. + Unsigned(u128), + /// Signed integer. + Signed(i128), +} + +/// The float value with any precision. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Float { + /// 32-bit float. + F32(f32), + /// 64-bit float. + F64(f64), +} + +impl<'a> Value<'a> { + /// Clone this value, borrowing the owned data where possible. + pub fn borrow_clone(&self) -> Value<'_> { + match self { + Value::Null => Value::Null, + Value::Bool(b) => Value::Bool(*b), + Value::Integer(int) => Value::Integer(*int), + Value::Float(float) => Value::Float(*float), + Value::Bytes(bytes) => Value::Bytes(Cow::Borrowed(bytes)), + Value::String(s) => Value::String(Cow::Borrowed(s)), + Value::Array(arr) => Value::Array(arr.iter().map(Self::borrow_clone).collect()), + Value::Map(map) => Value::Map( + map.iter().map(|(key, value)| (key.borrow_clone(), value.borrow_clone())).collect(), + ), + } + } + + /// Make value `'static` by cloning all borrowed data. + #[must_use] + pub fn into_owned(self) -> OwnedValue { + let value = match self { + Value::Null => Value::Null, + Value::Bool(b) => Value::Bool(b), + Value::Integer(int) => Value::Integer(int), + Value::Float(float) => Value::Float(float), + Value::Bytes(bytes) => Value::Bytes(bytes.into_owned().into()), + Value::String(s) => Value::String(s.into_owned().into()), + Value::Array(arr) => Value::Array(arr.into_iter().map(|v| v.into_owned().0).collect()), + Value::Map(map) => Value::Map( + map.into_iter() + .map(|(key, value)| (key.into_owned().0, value.into_owned().0)) + .collect(), + ), + }; + OwnedValue(value) + } + + /// Use this generic [Value] to deserialize into the given concrete type. + #[inline] + pub fn deserialize_as<'de, T>(self) -> Result + where + 'a: 'de, + T: Deserialize<'de>, + { + T::deserialize(de::ValueDeserializer::new(self)) + } + + /// Return whether the value is empty. This is the case if: + /// - The value is [Value::Null]. + /// - The value is [Value::Bytes] or [Value::String] of length 0. + /// - The value is [Value::Array] or [Value::Map] without items. + #[must_use] + #[inline] + pub fn is_empty(&self) -> bool { + match self { + Value::Null => true, + Value::Bool(_) | Value::Integer(_) | Value::Float(_) => false, + Value::Bytes(bytes) => bytes.is_empty(), + Value::String(s) => s.is_empty(), + Value::Array(arr) => arr.is_empty(), + Value::Map(map) => map.is_empty(), + } + } + + /// Return the inner bool if this is a [Value::Bool]. + #[must_use] + pub const fn as_bool(&self) -> Option { + if let Value::Bool(v) = self { + Some(*v) + } else { + None + } + } + + /// Return the inner int if this is a [Value::Integer]. + #[must_use] + pub const fn as_int(&self) -> Option { + if let Value::Integer(v) = self { + Some(*v) + } else { + None + } + } + + /// Return the inner float if this is a [Value::Float]. + #[must_use] + pub const fn as_float(&self) -> Option { + if let Value::Float(v) = self { + Some(*v) + } else { + None + } + } + + /// Return the inner bytes if this is a [Value::Bytes]. + #[must_use] + pub fn as_bytes(&self) -> Option<&[u8]> { + if let Value::Bytes(v) = self { + Some(v) + } else { + None + } + } + + /// Return the inner string if this is a [Value::String]. + #[must_use] + pub fn as_string(&self) -> Option<&str> { + if let Value::String(v) = self { + Some(v) + } else { + None + } + } + + /// Return the inner array if this is a [Value::Array]. + #[must_use] + pub const fn as_array(&self) -> Option<&VecDeque>> { + if let Value::Array(v) = self { + Some(v) + } else { + None + } + } + + /// Return the inner map if this is a [Value::Map]. + #[must_use] + pub const fn as_map(&self) -> Option<&VecDeque<(Value<'a>, Value<'a>)>> { + if let Value::Map(v) = self { + Some(v) + } else { + None + } + } + + /// Iterate over the inner values if this is a [Value::Array] or [Value::Map]. + #[must_use] + pub fn into_values(self) -> Iter> { + match self.into_owned().0 { + Value::Array(arr) => Iter::new(arr.into_iter()), + Value::Map(map) => Iter::new(map.into_iter().map(|(_key, value)| value)), + _ => Iter::new([].into_iter()), + } + } +} + +impl OwnedValue { + /// Create a new owned value. + #[must_use] + pub fn new(value: Value<'_>) -> Self { + value.into_owned() + } + + /// Return the inner value. + #[must_use] + pub fn into_inner(self) -> Value<'static> { + self.0 + } +} + +impl Deref for OwnedValue { + type Target = Value<'static>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OwnedValue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> ::core::fmt::Display for Value<'a> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Value::Null => f.write_str("null"), + Value::Bool(b) if *b => f.write_str("true"), + Value::Bool(_) => f.write_str("false"), + Value::Integer(int) => ::core::fmt::Display::fmt(int, f), + Value::Float(float) => ::core::fmt::Display::fmt(float, f), + Value::Bytes(bytes) => { + f.write_str("0x")?; + for (i, byte) in bytes.iter().enumerate() { + if i > 0 && i % 4 == 0 { + f.write_char('_')?; + } + write!(f, "{byte:02X}")?; + } + Ok(()) + } + Value::String(s) => f.write_str(s), + Value::Array(arr) => { + f.write_char('[')?; + for (i, value) in arr.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + ::core::fmt::Display::fmt(value, f)?; + } + f.write_char(']') + } + Value::Map(map) => { + f.write_char('{')?; + for (i, (key, value)) in map.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + ::core::fmt::Display::fmt(key, f)?; + f.write_str(": ")?; + ::core::fmt::Display::fmt(value, f)?; + } + f.write_char('}') + } + } + } +} + +impl ::core::fmt::Display for Integer { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Integer::Unsigned(int) => ::core::fmt::Display::fmt(int, f), + Integer::Signed(int) => ::core::fmt::Display::fmt(int, f), + } + } +} + +impl ::core::fmt::Display for Float { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + Float::F32(float) => ::core::fmt::Display::fmt(float, f), + Float::F64(float) => ::core::fmt::Display::fmt(float, f), + } + } +} + +impl<'a> Serialize for Value<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + match self { + Value::Null => serializer.serialize_none(), + Value::Bool(b) => serializer.serialize_bool(*b), + Value::Integer(Integer::Unsigned(int)) => serializer.serialize_u128(*int), + Value::Integer(Integer::Signed(int)) => serializer.serialize_i128(*int), + Value::Float(Float::F32(float)) => serializer.serialize_f32(*float), + Value::Float(Float::F64(float)) => serializer.serialize_f64(*float), + Value::Bytes(bytes) => serializer.serialize_bytes(bytes), + Value::String(s) => serializer.serialize_str(s), + Value::Array(arr) => { + use ::serde::ser::SerializeSeq; + + let mut ser = serializer.serialize_seq(Some(arr.len()))?; + for value in arr { + ser.serialize_element(value)?; + } + ser.end() + } + Value::Map(map) => { + use ::serde::ser::SerializeMap; + + let mut ser = serializer.serialize_map(Some(map.len()))?; + for (key, value) in map { + ser.serialize_entry(key, value)?; + } + ser.end() + } + } + } +} + +impl<'de> Deserialize<'de> for Value<'de> { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(ValueVisitor::default()) + } +} + +impl<'de> Deserialize<'de> for OwnedValue { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(ValueVisitor::default()).map(Value::into_owned) + } +} + +/// Serde [Visitor] for deserializing a [Value]. +#[derive(Debug, Clone, Copy, Default)] +struct ValueVisitor<'a>(PhantomData>); + +impl<'de> ::serde::de::Visitor<'de> for ValueVisitor<'de> { + type Value = Value<'de>; + + #[inline] + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + formatter.write_str("any value") + } + + #[inline] + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + Ok(Value::Bool(v)) + } + + #[inline] + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + self.visit_i128(i128::from(v)) + } + + #[inline] + fn visit_i128(self, v: i128) -> Result + where + E: serde::de::Error, + { + Ok(Value::Integer(Integer::Signed(v))) + } + + #[inline] + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + self.visit_u128(u128::from(v)) + } + + #[inline] + fn visit_u128(self, v: u128) -> Result + where + E: serde::de::Error, + { + Ok(Value::Integer(Integer::Unsigned(v))) + } + + #[inline] + fn visit_f32(self, v: f32) -> Result + where + E: serde::de::Error, + { + Ok(Value::Float(Float::F32(v))) + } + + #[inline] + fn visit_f64(self, v: f64) -> Result + where + E: serde::de::Error, + { + Ok(Value::Float(Float::F64(v))) + } + + #[inline] + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(Cow::Owned(v.to_owned()))) + } + + #[inline] + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(Cow::Borrowed(v))) + } + + #[inline] + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(Cow::Owned(v))) + } + + #[inline] + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(Value::Bytes(Cow::Owned(v.to_vec()))) + } + + #[inline] + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where + E: serde::de::Error, + { + Ok(Value::Bytes(Cow::Borrowed(v))) + } + + #[inline] + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + Ok(Value::Bytes(Cow::Owned(v))) + } + + #[inline] + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(Value::Null) + } + + #[inline] + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + #[inline] + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(Value::Null) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut arr = seq.size_hint().map_or_else(VecDeque::new, VecDeque::with_capacity); + + while let Some(value) = seq.next_element()? { + arr.push_back(value); + } + + Ok(Value::Array(arr)) + } + + #[inline] + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut entries = map.size_hint().map_or_else(VecDeque::new, VecDeque::with_capacity); + + while let Some((key, value)) = map.next_entry()? { + entries.push_back((key, value)); + } + + Ok(Value::Map(entries)) + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Null, Self::Null) => true, + (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, + (Self::Integer(l0), Self::Integer(r0)) => l0 == r0, + (Self::Float(l0), Self::Float(r0)) => l0 == r0, + (Self::Bytes(l0), Self::Bytes(r0)) => l0 == r0, + (Self::String(l0), Self::String(r0)) => l0 == r0, + (Self::Array(l0), Self::Array(r0)) => l0 == r0, + (Self::Map(l0), Self::Map(r0)) => l0 == r0, + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &bool) -> bool { + match self { + Value::Bool(b) => b == other, + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &u128) -> bool { + match self { + Value::Integer(Integer::Unsigned(int)) => int == other, + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &i128) -> bool { + match self { + Value::Integer(Integer::Signed(int)) => int == other, + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &f32) -> bool { + match self { + Value::Float(Float::F32(float)) => float == other, + Value::Float(Float::F64(float)) => *float == f64::from(*other), + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &f64) -> bool { + match self { + Value::Float(Float::F32(float)) => f64::from(*float) == *other, + Value::Float(Float::F64(float)) => float == other, + _ => false, + } + } +} + +impl<'a> PartialEq<[u8]> for Value<'a> { + fn eq(&self, other: &[u8]) -> bool { + match self { + Value::Bytes(bytes) => bytes.as_ref() == other, + _ => false, + } + } +} + +impl<'a> PartialEq for Value<'a> { + fn eq(&self, other: &str) -> bool { + match self { + Value::String(s) => s == other, + _ => false, + } + } +} + +impl<'a> From>> for Value<'a> { + #[inline] + fn from(value: Option>) -> Self { + value.unwrap_or_else(|| Value::Null) + } +} + +impl<'a> From<()> for Value<'a> { + #[inline] + fn from(_: ()) -> Self { + Value::Null + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: bool) -> Self { + Value::Bool(value) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: u8) -> Self { + Value::Integer(Integer::Unsigned(u128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: i8) -> Self { + Value::Integer(Integer::Signed(i128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: u16) -> Self { + Value::Integer(Integer::Unsigned(u128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: i16) -> Self { + Value::Integer(Integer::Signed(i128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: u32) -> Self { + Value::Integer(Integer::Unsigned(u128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: i32) -> Self { + Value::Integer(Integer::Signed(i128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: u64) -> Self { + Value::Integer(Integer::Unsigned(u128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: i64) -> Self { + Value::Integer(Integer::Signed(i128::from(value))) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: usize) -> Self { + Value::Integer(Integer::Unsigned(value as u128)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: isize) -> Self { + Value::Integer(Integer::Signed(value as i128)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: u128) -> Self { + Value::Integer(Integer::Unsigned(value)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: i128) -> Self { + Value::Integer(Integer::Signed(value)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: f32) -> Self { + Value::Float(Float::F32(value)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: f64) -> Self { + Value::Float(Float::F64(value)) + } +} + +impl<'a> From<&'a [u8]> for Value<'a> { + #[inline] + fn from(value: &'a [u8]) -> Self { + Value::Bytes(Cow::Borrowed(value)) + } +} + +impl<'a> From> for Value<'a> { + #[inline] + fn from(value: Vec) -> Self { + Value::Bytes(Cow::Owned(value)) + } +} + +impl<'a> From> for Value<'a> { + #[inline] + fn from(value: Cow<'a, [u8]>) -> Self { + Value::Bytes(value) + } +} + +impl<'a> From<&'a str> for Value<'a> { + #[inline] + fn from(value: &'a str) -> Self { + Value::String(Cow::Borrowed(value)) + } +} + +impl<'a> From for Value<'a> { + #[inline] + fn from(value: String) -> Self { + Value::String(Cow::Owned(value)) + } +} + +impl<'a> From> for Value<'a> { + #[inline] + fn from(value: Cow<'a, str>) -> Self { + Value::String(value) + } +} + +impl<'a> From>> for Value<'a> { + #[inline] + fn from(value: VecDeque>) -> Self { + Value::Array(value) + } +} + +impl<'a> From, Value<'a>)>> for Value<'a> { + #[inline] + fn from(value: VecDeque<(Value<'a>, Value<'a>)>) -> Self { + Value::Map(value) + } +} + +impl<'a, T> FromIterator for Value<'a> +where + T: Into>, +{ + #[inline] + fn from_iter>(iter: I) -> Self { + Self::from(iter.into_iter().map(Into::into).collect::>()) + } +} + +impl<'a, K, V> FromIterator<(K, V)> for Value<'a> +where + K: Into>, + V: Into>, +{ + #[inline] + fn from_iter>(iter: I) -> Self { + Self::from(iter.into_iter().map(|(k, v)| (k.into(), v.into())).collect::>()) + } +} + +/// Trait for trait objects of exact size iterators. +trait AllIteratorTrait: + Iterator + ExactSizeIterator + DoubleEndedIterator + ::core::fmt::Debug +{ +} +impl AllIteratorTrait for I where + I: Iterator + ExactSizeIterator + DoubleEndedIterator + ::core::fmt::Debug +{ +} + +/// Generic iterator. +#[derive(Debug)] +pub struct Iter(Box + Send + Sync>); + +impl Iter { + /// Create a new generic iterator. + fn new(iter: I) -> Self + where + I: AllIteratorTrait + Send + Sync + 'static, + { + Self(Box::new(iter)) + } +} + +impl Iterator for Iter { + type Item = T; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option + where + Self: Sized, + { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } +} + +impl ExactSizeIterator for Iter { + fn len(&self) -> usize { + self.0.len() + } +} + +impl DoubleEndedIterator for Iter { + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } +} + + +#[cfg(test)] +mod tests; diff --git a/src/value/ser.rs b/src/value/ser.rs new file mode 100644 index 0000000..e05d2c9 --- /dev/null +++ b/src/value/ser.rs @@ -0,0 +1,530 @@ +//! Serialization from any type into a [Value]. +#![cfg_attr( + feature = "tracing", + allow(clippy::used_underscore_binding, reason = "Only used in tracing::instrument") +)] + +use ::alloc::{borrow::ToOwned, vec}; + +use super::*; +use crate::{Error, Result}; + +/// Serializer to serialize any type into a [Value]. +#[derive(Debug, Clone, Copy)] +pub struct ValueSerializer { + /// Whether to use the `use_indices` format. + use_indices: bool, +} + +impl ValueSerializer { + /// Create a new serializer. + #[must_use] + pub const fn new(use_indices: bool) -> Self { + Self { use_indices } + } +} + +impl ::serde::ser::Serializer for ValueSerializer { + type Ok = Value<'static>; + type Error = Error; + type SerializeSeq = ValueSeqSerializer; + type SerializeTuple = ValueSeqSerializer; + type SerializeTupleStruct = ValueSeqSerializer; + type SerializeTupleVariant = ValueSeqVariantSerializer; + type SerializeMap = ValueMapSerializer; + type SerializeStruct = ValueMapSerializer; + type SerializeStructVariant = ValueMapVariantSerializer; + + #[inline] + fn is_human_readable(&self) -> bool { + true + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_bool(self, v: bool) -> Result { + Ok(Value::Bool(v)) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i8(self, v: i8) -> Result { + Ok(Value::Integer(Integer::Signed(i128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i16(self, v: i16) -> Result { + Ok(Value::Integer(Integer::Signed(i128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i32(self, v: i32) -> Result { + Ok(Value::Integer(Integer::Signed(i128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i64(self, v: i64) -> Result { + Ok(Value::Integer(Integer::Signed(i128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u8(self, v: u8) -> Result { + Ok(Value::Integer(Integer::Unsigned(u128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u16(self, v: u16) -> Result { + Ok(Value::Integer(Integer::Unsigned(u128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u32(self, v: u32) -> Result { + Ok(Value::Integer(Integer::Unsigned(u128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u64(self, v: u64) -> Result { + Ok(Value::Integer(Integer::Unsigned(u128::from(v)))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_i128(self, v: i128) -> Result { + Ok(Value::Integer(Integer::Signed(v))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_u128(self, v: u128) -> Result { + Ok(Value::Integer(Integer::Unsigned(v))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_f32(self, v: f32) -> Result { + Ok(Value::Float(Float::F32(v))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_f64(self, v: f64) -> Result { + Ok(Value::Float(Float::F64(v))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_char(self, v: char) -> Result { + let mut buffer = [0; 4]; + let s = v.encode_utf8(&mut buffer); + self.serialize_str(s) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_str(self, v: &str) -> Result { + Ok(Value::String(Cow::Owned(v.to_owned()))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Value::Bytes(Cow::Owned(v.to_owned()))) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_none(self) -> Result { + Ok(Value::Null) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + serde::Serialize, + { + value.serialize(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit(self) -> Result { + Ok(Value::Null) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(Value::Null) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_unit_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + if self.use_indices { + Ok(Value::Integer(Integer::Unsigned(u128::from(variant_index)))) + } else { + Ok(Value::String(Cow::Borrowed(variant))) + } + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + value.serialize(self) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_newtype_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + let key = if self.use_indices { + Value::Integer(Integer::Unsigned(u128::from(variant_index))) + } else { + Value::String(Cow::Borrowed(variant)) + }; + let value = value.serialize(self)?; + Ok(Value::Map(vec![(key, value)].into())) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_seq(self, len: Option) -> Result { + let arr = len.map_or_else(VecDeque::new, VecDeque::with_capacity); + Ok(ValueSeqSerializer { serializer: self, arr }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple(self, len: usize) -> Result { + let arr = VecDeque::with_capacity(len); + Ok(ValueSeqSerializer { serializer: self, arr }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + let arr = VecDeque::with_capacity(len); + Ok(ValueSeqSerializer { serializer: self, arr }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_tuple_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let key = if self.use_indices { + Value::Integer(Integer::Unsigned(u128::from(variant_index))) + } else { + Value::String(Cow::Borrowed(variant)) + }; + Ok(ValueSeqVariantSerializer { serializer: self, key, arr: VecDeque::with_capacity(len) }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_map(self, len: Option) -> Result { + let map = len.map_or_else(VecDeque::new, VecDeque::with_capacity); + Ok(ValueMapSerializer { serializer: self, map, field_index: 0 }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + let map = VecDeque::with_capacity(len); + Ok(ValueMapSerializer { serializer: self, map, field_index: 0 }) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn serialize_struct_variant( + self, + _name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let key = if self.use_indices { + Value::Integer(Integer::Unsigned(u128::from(variant_index))) + } else { + Value::String(Cow::Borrowed(variant)) + }; + Ok(ValueMapVariantSerializer { + serializer: self, + key, + map: VecDeque::with_capacity(len), + field_index: 0, + }) + } +} + +/// Serializer for a sequence of values. +#[derive(Debug)] +pub struct ValueSeqSerializer { + /// Serializer. + serializer: ValueSerializer, + /// Values. + arr: VecDeque>, +} + +impl ::serde::ser::SerializeSeq for ValueSeqSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.arr.push_back(value.serialize(self.serializer)?); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Array(self.arr)) + } +} + +impl ::serde::ser::SerializeTuple for ValueSeqSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.arr.push_back(value.serialize(self.serializer)?); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Array(self.arr)) + } +} + +impl ::serde::ser::SerializeTupleStruct for ValueSeqSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.arr.push_back(value.serialize(self.serializer)?); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Array(self.arr)) + } +} + +/// Serializer for a map of keys and values. +#[derive(Debug)] +pub struct ValueMapSerializer { + /// Serializer. + serializer: ValueSerializer, + /// Values. + map: VecDeque<(Value<'static>, Value<'static>)>, + /// The current field index. + field_index: u32, +} + +impl ::serde::ser::SerializeMap for ValueMapSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let key = key.serialize(self.serializer)?; + self.map.push_back((key, Value::Null)); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + #![expect(clippy::expect_used, reason = "Serde requirement")] + self.map.back_mut().expect("serialize_key is called before serialize_value").1 = + value.serialize(self.serializer)?; + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Map(self.map)) + } +} + +impl ::serde::ser::SerializeStruct for ValueMapSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let key = if self.serializer.use_indices { + Value::Integer(Integer::Unsigned(u128::from(self.field_index))) + } else { + Value::String(Cow::Borrowed(key)) + }; + self.field_index += 1; + let value = value.serialize(self.serializer)?; + self.map.push_back((key, value)); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Map(self.map)) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn skip_field(&mut self, _key: &'static str) -> Result<(), Self::Error> { + self.field_index += 1; + Ok(()) + } +} + +/// Serializer for sequence variants in enums. +#[derive(Debug)] +pub struct ValueSeqVariantSerializer { + /// Serializer. + serializer: ValueSerializer, + /// Variant key. + key: Value<'static>, + /// Values. + arr: VecDeque>, +} + +impl ::serde::ser::SerializeTupleVariant for ValueSeqVariantSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.arr.push_back(value.serialize(self.serializer)?); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip_all))] + fn end(self) -> Result { + Ok(Value::Map(vec![(self.key, Value::Array(self.arr))].into())) + } +} + +/// Serializer for map variants in enums. +#[derive(Debug)] +pub struct ValueMapVariantSerializer { + /// Serializer. + serializer: ValueSerializer, + /// Variant key. + key: Value<'static>, + /// Values. + map: VecDeque<(Value<'static>, Value<'static>)>, + /// The current field index. + field_index: u32, +} + +impl ::serde::ser::SerializeStructVariant for ValueMapVariantSerializer { + type Ok = Value<'static>; + type Error = Error; + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self, value)))] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let key = if self.serializer.use_indices { + Value::Integer(Integer::Unsigned(u128::from(self.field_index))) + } else { + Value::String(Cow::Borrowed(key)) + }; + self.field_index += 1; + let value = value.serialize(self.serializer)?; + self.map.push_back((key, value)); + Ok(()) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn end(self) -> Result { + Ok(Value::Map(vec![(self.key, Value::Map(self.map))].into())) + } + + #[inline] + #[cfg_attr(feature = "tracing", ::tracing::instrument(skip(self)))] + fn skip_field(&mut self, _key: &'static str) -> Result<(), Self::Error> { + self.field_index += 1; + Ok(()) + } +} diff --git a/src/value/tests.rs b/src/value/tests.rs new file mode 100644 index 0000000..05687d7 --- /dev/null +++ b/src/value/tests.rs @@ -0,0 +1,833 @@ +//! Generic [Value] tests. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::print_stdout, reason = "Tests")] +#![allow(clippy::too_many_lines, reason = "Byte lists and such :P")] + +use ::alloc::{borrow::ToOwned, vec}; +use ::core::fmt::Debug; +use ::serde::de::DeserializeOwned; +use ::serde_bytes::ByteBuf; + +use super::*; +use crate::{format::Type, tests::init_tracing}; + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(expected_value)))] +fn test_serde(value: &T, expected_value: &Value<'_>) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let ir_value = crate::to_value(&value).unwrap(); + assert_eq!(ir_value, *expected_value); + let bytes = crate::to_vec(&value).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Byte representation: {bytes:?}"); + let ir_value: OwnedValue = crate::from_slice(bytes.as_slice()).unwrap(); + let ir_value = ir_value.into_inner(); + assert_eq!(ir_value, *expected_value); + let deserialized: T = crate::from_value(ir_value).unwrap(); + assert_eq!(deserialized, *value); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(expected_value)))] +fn test_serde_with_indices(value: &T, expected_value: &Value<'_>) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let config = Config { use_indices: true, ..Default::default() }; + let ir_value = crate::to_value_with_config(&value, config).unwrap(); + assert_eq!(ir_value, *expected_value); + let bytes = crate::to_vec_with_config(&value, config).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Byte representation: {bytes:?}"); + let ir_value: OwnedValue = crate::from_slice_with_config(bytes.as_slice(), config).unwrap(); + let ir_value = ir_value.into_inner(); + assert_eq!(ir_value, *expected_value); + let deserialized: T = crate::from_value_with_config(ir_value, config).unwrap(); + assert_eq!(deserialized, *value); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(expected_value)))] +fn test_deser<'de, T>(bytes: &'de [u8], expected_value: &Value<'_>) +where + T: Deserialize<'de> + Serialize + Debug, +{ + let value: Value<'de> = crate::from_slice(bytes).unwrap(); + assert_eq!(value, *expected_value); + let deserialized: T = crate::from_value(value).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Deserialized value: {deserialized:?}"); + let value = crate::to_value(&deserialized).unwrap(); + assert_eq!(value, *expected_value); + let serialized = crate::to_vec(&value).unwrap(); + assert_eq!(serialized, bytes); +} + +#[cfg_attr(feature = "tracing", ::tracing::instrument(skip(expected_value)))] +fn test_deser_with_indices<'de, T>(bytes: &'de [u8], expected_value: &Value<'_>) +where + T: Deserialize<'de> + Serialize + Debug, +{ + let config = Config { use_indices: true, ..Default::default() }; + let value: Value<'de> = crate::from_slice_with_config(bytes, config).unwrap(); + assert_eq!(value, *expected_value); + let deserialized: T = crate::from_value_with_config(value, config).unwrap(); + #[cfg(feature = "tracing")] + tracing::info!("Deserialized value: {deserialized:?}"); + let value = crate::to_value_with_config(&deserialized, config).unwrap(); + assert_eq!(value, *expected_value); + let serialized = crate::to_vec_with_config(&value, config).unwrap(); + assert_eq!(serialized, bytes); +} + + +#[test] +fn test_unit() { + init_tracing(); + test_serde(&(), &Value::Null); + test_serde_with_indices(&(), &Value::Null); + test_deser::<()>(&[Type::Null.into()], &Value::Null); + test_deser_with_indices::<()>(&[Type::Null.into()], &Value::Null); +} + +#[test] +fn test_boolean() { + init_tracing(); + test_serde(&true, &Value::Bool(true)); + test_serde_with_indices(&true, &Value::Bool(true)); + test_deser::(&[Type::BooleanFalse.into()], &Value::Bool(false)); + test_deser_with_indices::(&[Type::BooleanFalse.into()], &Value::Bool(false)); + test_serde(&false, &Value::Bool(false)); + test_serde_with_indices(&false, &Value::Bool(false)); + test_deser::(&[Type::BooleanTrue.into()], &Value::Bool(true)); + test_deser_with_indices::(&[Type::BooleanTrue.into()], &Value::Bool(true)); +} + +#[test] +fn test_unsigned_int() { + init_tracing(); + test_serde(&125_u8, &Value::Integer(Integer::Unsigned(125))); + test_serde_with_indices(&125_u8, &Value::Integer(Integer::Unsigned(125))); + test_serde(&125_usize, &Value::Integer(Integer::Unsigned(125))); + test_serde_with_indices(&125_usize, &Value::Integer(Integer::Unsigned(125))); + test_serde(&125_u128, &Value::Integer(Integer::Unsigned(125))); + test_serde_with_indices(&125_u128, &Value::Integer(Integer::Unsigned(125))); + test_serde( + &0x0123_4567_89AB_CDEF_u64, + &Value::Integer(Integer::Unsigned(0x0123_4567_89AB_CDEF)), + ); + test_serde_with_indices( + &0x0123_4567_89AB_CDEF_u64, + &Value::Integer(Integer::Unsigned(0x0123_4567_89AB_CDEF)), + ); + test_deser::(&[Type::UnsignedInt.into(), 0x00], &Value::Integer(Integer::Unsigned(0))); + test_deser_with_indices::( + &[Type::UnsignedInt.into(), 0x00], + &Value::Integer(Integer::Unsigned(0)), + ); + test_deser::( + &[Type::UnsignedInt.into(), 0xFF, 0x01], + &Value::Integer(Integer::Unsigned(0xFF)), + ); + test_deser_with_indices::( + &[Type::UnsignedInt.into(), 0xFF, 0x01], + &Value::Integer(Integer::Unsigned(0xFF)), + ); +} + +#[test] +fn test_signed_int() { + init_tracing(); + test_serde(&125_i8, &Value::Integer(Integer::Signed(125))); + test_serde_with_indices(&125_i8, &Value::Integer(Integer::Signed(125))); + test_serde(&-125_isize, &Value::Integer(Integer::Signed(-125))); + test_serde_with_indices(&-125_isize, &Value::Integer(Integer::Signed(-125))); + test_serde(&125_i128, &Value::Integer(Integer::Signed(125))); + test_serde_with_indices(&125_i128, &Value::Integer(Integer::Signed(125))); + test_serde(&0x0123_4567_89AB_CDEF_i64, &Value::Integer(Integer::Signed(0x0123_4567_89AB_CDEF))); + test_serde_with_indices( + &0x0123_4567_89AB_CDEF_i64, + &Value::Integer(Integer::Signed(0x0123_4567_89AB_CDEF)), + ); + test_serde( + &-0x0123_4567_89AB_CDEF_i64, + &Value::Integer(Integer::Signed(-0x0123_4567_89AB_CDEF)), + ); + test_serde_with_indices( + &-0x0123_4567_89AB_CDEF_i64, + &Value::Integer(Integer::Signed(-0x0123_4567_89AB_CDEF)), + ); + test_deser::(&[Type::SignedInt.into(), 0x00], &Value::Integer(Integer::Signed(0))); + test_deser_with_indices::( + &[Type::SignedInt.into(), 0x00], + &Value::Integer(Integer::Signed(0)), + ); + test_deser::(&[Type::SignedInt.into(), 0x01], &Value::Integer(Integer::Signed(-1))); + test_deser_with_indices::( + &[Type::SignedInt.into(), 0x01], + &Value::Integer(Integer::Signed(-1)), + ); + test_deser::( + &[Type::SignedInt.into(), 0xFE, 0x01], + &Value::Integer(Integer::Signed(127)), + ); + test_deser_with_indices::( + &[Type::SignedInt.into(), 0xFE, 0x01], + &Value::Integer(Integer::Signed(127)), + ); + test_deser::( + &[Type::SignedInt.into(), 0xFF, 0x01], + &Value::Integer(Integer::Signed(-128)), + ); + test_deser_with_indices::( + &[Type::SignedInt.into(), 0xFF, 0x01], + &Value::Integer(Integer::Signed(-128)), + ); +} + +#[test] +fn test_floats() { + init_tracing(); + test_serde(&3.5_f32, &Value::Float(Float::F32(3.5))); + test_serde_with_indices(&3.5_f32, &Value::Float(Float::F32(3.5))); + test_serde(&3.5_f64, &Value::Float(Float::F64(3.5))); + test_serde_with_indices(&3.5_f64, &Value::Float(Float::F64(3.5))); + test_serde(&-3.5_f64, &Value::Float(Float::F64(-3.5))); + test_serde_with_indices(&-3.5_f64, &Value::Float(Float::F64(-3.5))); + test_deser::( + &[Type::Float32.into(), 0x12, 0x34, 0x56, 0x78], + &Value::Float(Float::F32(f32::from_le_bytes([0x12, 0x34, 0x56, 0x78]))), + ); + test_deser_with_indices::( + &[Type::Float32.into(), 0x12, 0x34, 0x56, 0x78], + &Value::Float(Float::F32(f32::from_le_bytes([0x12, 0x34, 0x56, 0x78]))), + ); + test_deser::( + &[Type::Float64.into(), 1, 2, 3, 4, 5, 6, 7, 8], + &Value::Float(Float::F64(f64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]))), + ); + test_deser_with_indices::( + &[Type::Float64.into(), 1, 2, 3, 4, 5, 6, 7, 8], + &Value::Float(Float::F64(f64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]))), + ); +} + +#[test] +fn test_bytes() { + init_tracing(); + test_serde( + &ByteBuf::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + &Value::Bytes(Cow::Borrowed(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + ); + test_serde_with_indices( + &ByteBuf::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + &Value::Bytes(Cow::Borrowed(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + ); + test_deser::( + &[Type::Bytes.into(), 5, 1, 2, 3, 4, 5], + &Value::Bytes(Cow::Borrowed(&[1, 2, 3, 4, 5])), + ); + test_deser_with_indices::( + &[Type::Bytes.into(), 5, 1, 2, 3, 4, 5], + &Value::Bytes(Cow::Borrowed(&[1, 2, 3, 4, 5])), + ); +} + +#[test] +fn test_string() { + init_tracing(); + test_serde( + &"I was serialized and deserialized!".to_owned(), + &Value::String(Cow::Borrowed("I was serialized and deserialized!")), + ); + test_serde_with_indices( + &"I was serialized and deserialized!".to_owned(), + &Value::String(Cow::Borrowed("I was serialized and deserialized!")), + ); + test_deser::<&str>( + &[Type::String.into(), 5, b'H', b'e', b'l', b'l', b'o'], + &Value::String(Cow::Borrowed("Hello")), + ); + test_deser_with_indices::<&str>( + &[Type::String.into(), 5, b'H', b'e', b'l', b'l', b'o'], + &Value::String(Cow::Borrowed("Hello")), + ); +} + +#[test] +fn test_char() { + init_tracing(); + test_serde(&'😻', &Value::String(Cow::Borrowed("😻"))); + test_serde_with_indices(&'😻', &Value::String(Cow::Borrowed("😻"))); + test_deser::(&[Type::String.into(), 1, b'x'], &Value::String(Cow::Borrowed("x"))); + test_deser_with_indices::( + &[Type::String.into(), 1, b'x'], + &Value::String(Cow::Borrowed("x")), + ); +} + +#[test] +fn test_sequence() { + init_tracing(); + test_serde(&[0; 0], &Value::Array(vec![].into())); + test_serde_with_indices(&[0; 0], &Value::Array(vec![].into())); + test_serde( + &[true, false, true, false], + &Value::Array( + vec![Value::Bool(true), Value::Bool(false), Value::Bool(true), Value::Bool(false)] + .into(), + ), + ); + test_serde_with_indices( + &[true, false, true, false], + &Value::Array( + vec![Value::Bool(true), Value::Bool(false), Value::Bool(true), Value::Bool(false)] + .into(), + ), + ); + test_deser::<[bool; 0]>( + &[Type::SeqStart.into(), Type::SeqEnd.into()], + &Value::Array(vec![].into()), + ); + test_deser::<[Option; 3]>( + &[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ], + &Value::Array(vec![Value::Null, Value::Bool(false), Value::Bool(true)].into()), + ); + test_deser_with_indices::<[bool; 0]>( + &[Type::SeqStart.into(), Type::SeqEnd.into()], + &Value::Array(vec![].into()), + ); + test_deser_with_indices::<[Option; 3]>( + &[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ], + &Value::Array(vec![Value::Null, Value::Bool(false), Value::Bool(true)].into()), + ); +} + +#[test] +fn test_tuple() { + init_tracing(); + test_serde(&(1,), &Value::Array(vec![Value::Integer(Integer::Signed(1))].into())); + test_serde_with_indices(&(1,), &Value::Array(vec![Value::Integer(Integer::Signed(1))].into())); + test_serde( + &(1, 'h', 'i', 8), + &Value::Array( + vec![ + Value::Integer(Integer::Signed(1)), + Value::String(Cow::Borrowed("h")), + Value::String(Cow::Borrowed("i")), + Value::Integer(Integer::Signed(8)), + ] + .into(), + ), + ); + test_serde_with_indices( + &(1, 'h', 'i', 8), + &Value::Array( + vec![ + Value::Integer(Integer::Signed(1)), + Value::String(Cow::Borrowed("h")), + Value::String(Cow::Borrowed("i")), + Value::Integer(Integer::Signed(8)), + ] + .into(), + ), + ); + test_deser::<(&str,)>( + &[Type::SeqStart.into(), Type::String.into(), 0, Type::SeqEnd.into()], + &Value::Array(vec![Value::String(Cow::Borrowed(""))].into()), + ); + test_deser::<((), bool, Option, &str)>( + &[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::String.into(), + 0, + Type::SeqEnd.into(), + ], + &Value::Array( + vec![Value::Null, Value::Bool(false), Value::Bool(true), Value::String("".into())] + .into(), + ), + ); + test_deser_with_indices::<(&str,)>( + &[Type::SeqStart.into(), Type::String.into(), 0, Type::SeqEnd.into()], + &Value::Array(vec![Value::String(Cow::Borrowed(""))].into()), + ); + test_deser_with_indices::<((), bool, Option, &str)>( + &[ + Type::SeqStart.into(), + Type::Null.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::String.into(), + 0, + Type::SeqEnd.into(), + ], + &Value::Array( + vec![Value::Null, Value::Bool(false), Value::Bool(true), Value::String("".into())] + .into(), + ), + ); +} + +#[test] +fn test_map() { + use ::alloc::collections::BTreeMap; + + init_tracing(); + + let mut map = BTreeMap::new(); + map.insert("null".to_owned(), 0); + map.insert("one".to_owned(), 1); + map.insert("two".to_owned(), 2); + map.insert("three".to_owned(), 3); + map.insert("four".to_owned(), 4); + test_serde( + &map, + &Value::Map( + vec![ + (Value::String("four".into()), Value::Integer(Integer::Signed(4))), + (Value::String("null".into()), Value::Integer(Integer::Signed(0))), + (Value::String("one".into()), Value::Integer(Integer::Signed(1))), + (Value::String("three".into()), Value::Integer(Integer::Signed(3))), + (Value::String("two".into()), Value::Integer(Integer::Signed(2))), + ] + .into(), + ), + ); + test_serde_with_indices( + &map, + &Value::Map( + vec![ + (Value::String("four".into()), Value::Integer(Integer::Signed(4))), + (Value::String("null".into()), Value::Integer(Integer::Signed(0))), + (Value::String("one".into()), Value::Integer(Integer::Signed(1))), + (Value::String("three".into()), Value::Integer(Integer::Signed(3))), + (Value::String("two".into()), Value::Integer(Integer::Signed(2))), + ] + .into(), + ), + ); + test_deser::>( + &[Type::MapStart.into(), Type::MapEnd.into()], + &Value::Map(vec![].into()), + ); + test_deser::>( + &[ + Type::MapStart.into(), + Type::BooleanFalse.into(), + Type::String.into(), + 0, + Type::MapEnd.into(), + ], + &Value::Map(vec![(Value::Bool(false), Value::String("".into()))].into()), + ); + test_deser_with_indices::>( + &[Type::MapStart.into(), Type::MapEnd.into()], + &Value::Map(vec![].into()), + ); + test_deser_with_indices::>( + &[ + Type::MapStart.into(), + Type::BooleanFalse.into(), + Type::String.into(), + 0, + Type::MapEnd.into(), + ], + &Value::Map(vec![(Value::Bool(false), Value::String("".into()))].into()), + ); + test_deser_with_indices::>( + &[ + Type::MapStart.into(), + Type::String.into(), + 0, + Type::BooleanFalse.into(), + Type::MapEnd.into(), + ], + &Value::Map(vec![(Value::String("".into()), Value::Bool(false))].into()), + ); +} + +#[test] +fn test_option() { + init_tracing(); + test_serde(&None::, &Value::Null); + test_serde_with_indices(&None::, &Value::Null); + test_serde(&Some(true), &Value::Bool(true)); + test_serde_with_indices(&Some(true), &Value::Bool(true)); + test_serde(&None::, &Value::Null); + test_serde_with_indices(&None::, &Value::Null); + test_serde(&Some('a'), &Value::String("a".into())); + test_serde_with_indices(&Some('a'), &Value::String("a".into())); + test_serde(&None::, &Value::Null); + test_serde_with_indices(&None::, &Value::Null); + test_serde(&Some(5), &Value::Integer(Integer::Signed(5))); + test_serde_with_indices(&Some(5), &Value::Integer(Integer::Signed(5))); + test_deser::>(&[Type::Null.into()], &Value::Null); + test_deser::>(&[Type::SignedInt.into(), 5], &Value::Integer(Integer::Signed(-3))); + test_deser_with_indices::>(&[Type::Null.into()], &Value::Null); + test_deser_with_indices::>( + &[Type::SignedInt.into(), 5], + &Value::Integer(Integer::Signed(-3)), + ); +} + +#[test] +fn test_empty_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct EmptyStruct {} + + init_tracing(); + test_serde(&EmptyStruct {}, &Value::Map(vec![].into())); + test_serde_with_indices(&EmptyStruct {}, &Value::Map(vec![].into())); + test_deser::( + &[Type::MapStart.into(), Type::MapEnd.into()], + &Value::Map(vec![].into()), + ); + test_deser_with_indices::( + &[Type::MapStart.into(), Type::MapEnd.into()], + &Value::Map(vec![].into()), + ); +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Struct { + a: bool, + b: bool, + } + + init_tracing(); + test_serde( + &Struct { a: false, b: true }, + &Value::Map( + vec![ + (Value::String("a".into()), Value::Bool(false)), + (Value::String("b".into()), Value::Bool(true)), + ] + .into(), + ), + ); + test_serde_with_indices( + &Struct { a: false, b: true }, + &Value::Map( + vec![ + (Value::Integer(Integer::Unsigned(0)), Value::Bool(false)), + (Value::Integer(Integer::Unsigned(1)), Value::Bool(true)), + ] + .into(), + ), + ); + test_deser::( + &[ + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ], + &Value::Map( + vec![ + (Value::String("a".into()), Value::Bool(false)), + (Value::String("b".into()), Value::Bool(true)), + ] + .into(), + ), + ); + test_deser_with_indices::( + &[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + ], + &Value::Map( + vec![ + (Value::Integer(Integer::Unsigned(0)), Value::Bool(false)), + (Value::Integer(Integer::Unsigned(1)), Value::Bool(true)), + ] + .into(), + ), + ); +} + +#[test] +fn test_newtype_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct NewtypeStruct(bool); + + init_tracing(); + test_serde(&NewtypeStruct(false), &Value::Bool(false)); + test_serde_with_indices(&NewtypeStruct(false), &Value::Bool(false)); + test_deser::(&[Type::BooleanTrue.into()], &Value::Bool(true)); + test_deser_with_indices::(&[Type::BooleanTrue.into()], &Value::Bool(true)); +} + +#[test] +fn test_tuple_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct TupleStruct(bool, bool); + + init_tracing(); + let value = Value::Array(vec![Value::Bool(false), Value::Bool(true)].into()); + + test_serde(&TupleStruct(false, true), &value); + test_serde_with_indices(&TupleStruct(false, true), &value); + test_deser::( + &[ + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ], + &value, + ); + test_deser_with_indices::( + &[ + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + ], + &value, + ); +} + +#[test] +fn test_enums() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum EnumVariants { + Unit, + Newtype(bool), + Tuple(bool, bool), + Struct { a: bool, b: bool }, + } + + init_tracing(); + + let val_unit = Value::String("Unit".into()); + let val_unit_index = Value::Integer(Integer::Unsigned(0)); + let val_newtype = + Value::Map(vec![(Value::String("Newtype".into()), Value::Bool(false))].into()); + let val_newtype_index = + Value::Map(vec![(Value::Integer(Integer::Unsigned(1)), Value::Bool(false))].into()); + let val_tuple = Value::Map( + vec![( + Value::String("Tuple".into()), + Value::Array(vec![Value::Bool(false), Value::Bool(true)].into()), + )] + .into(), + ); + let val_tuple_index = Value::Map( + vec![( + Value::Integer(Integer::Unsigned(2)), + Value::Array(vec![Value::Bool(false), Value::Bool(true)].into()), + )] + .into(), + ); + let val_struct = Value::Map( + vec![( + Value::String("Struct".into()), + Value::Map( + vec![ + (Value::String("a".into()), Value::Bool(false)), + (Value::String("b".into()), Value::Bool(true)), + ] + .into(), + ), + )] + .into(), + ); + let val_struct_index = Value::Map( + vec![( + Value::Integer(Integer::Unsigned(3)), + Value::Map( + vec![ + (Value::Integer(Integer::Unsigned(0)), Value::Bool(false)), + (Value::Integer(Integer::Unsigned(1)), Value::Bool(true)), + ] + .into(), + ), + )] + .into(), + ); + + test_serde(&EnumVariants::Unit, &val_unit); + test_serde_with_indices(&EnumVariants::Unit, &val_unit_index); + test_serde(&EnumVariants::Newtype(false), &val_newtype); + test_serde_with_indices(&EnumVariants::Newtype(false), &val_newtype_index); + test_serde(&EnumVariants::Tuple(false, true), &val_tuple); + test_serde_with_indices(&EnumVariants::Tuple(false, true), &val_tuple_index); + test_serde(&EnumVariants::Struct { a: false, b: true }, &val_struct); + test_serde_with_indices(&EnumVariants::Struct { a: false, b: true }, &val_struct_index); + test_serde( + &[ + EnumVariants::Unit, + EnumVariants::Newtype(false), + EnumVariants::Tuple(false, true), + EnumVariants::Struct { a: false, b: true }, + ], + &Value::Array( + vec![val_unit.clone(), val_newtype.clone(), val_tuple.clone(), val_struct.clone()] + .into(), + ), + ); + test_serde_with_indices( + &[ + EnumVariants::Unit, + EnumVariants::Newtype(false), + EnumVariants::Tuple(false, true), + EnumVariants::Struct { a: false, b: true }, + ], + &Value::Array( + vec![ + val_unit_index.clone(), + val_newtype_index.clone(), + val_tuple_index.clone(), + val_struct_index.clone(), + ] + .into(), + ), + ); + + test_deser::(&[Type::String.into(), 4, b'U', b'n', b'i', b't'], &val_unit); + test_deser::( + &[ + Type::MapStart.into(), + Type::String.into(), + 7, + b'N', + b'e', + b'w', + b't', + b'y', + b'p', + b'e', + Type::BooleanFalse.into(), + Type::MapEnd.into(), + ], + &val_newtype, + ); + test_deser::( + &[ + Type::MapStart.into(), + Type::String.into(), + 5, + b'T', + b'u', + b'p', + b'l', + b'e', + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + Type::MapEnd.into(), + ], + &val_tuple, + ); + test_deser_with_indices::( + &[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 2, + Type::SeqStart.into(), + Type::BooleanFalse.into(), + Type::BooleanTrue.into(), + Type::SeqEnd.into(), + Type::MapEnd.into(), + ], + &val_tuple_index, + ); + test_deser::( + &[ + Type::MapStart.into(), + Type::String.into(), + 6, + b'S', + b't', + b'r', + b'u', + b'c', + b't', + Type::MapStart.into(), + Type::String.into(), + 1, + b'a', + Type::BooleanFalse.into(), + Type::String.into(), + 1, + b'b', + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ], + &val_struct, + ); + test_deser_with_indices::( + &[ + Type::MapStart.into(), + Type::UnsignedInt.into(), + 3, + Type::MapStart.into(), + Type::UnsignedInt.into(), + 0, + Type::BooleanFalse.into(), + Type::UnsignedInt.into(), + 1, + Type::BooleanTrue.into(), + Type::MapEnd.into(), + Type::MapEnd.into(), + ], + &val_struct_index, + ); +} + +#[test] +fn test_enums_with_discriminants() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum Enum { + VarA = 5, + VarB = 10, + } + + init_tracing(); + let val_a = Value::String("VarA".into()); + let val_b = Value::String("VarB".into()); + // Serde expect indices, not discriminants. + let val_a_index = Value::Integer(Integer::Unsigned(0)); + let val_b_index = Value::Integer(Integer::Unsigned(1)); + + test_serde(&Enum::VarA, &val_a); + test_serde(&Enum::VarB, &val_b); + test_serde_with_indices(&Enum::VarA, &val_a_index); + test_serde_with_indices(&Enum::VarB, &val_b_index); + + test_deser::(&[Type::String.into(), 4, b'V', b'a', b'r', b'A'], &val_a); + test_deser::(&[Type::String.into(), 4, b'V', b'a', b'r', b'B'], &val_b); + // Serde expect indices, not discriminants. + test_deser_with_indices::(&[Type::UnsignedInt.into(), 0], &val_a_index); + test_deser_with_indices::(&[Type::UnsignedInt.into(), 1], &val_b_index); +} diff --git a/tests/all/json_data.rs b/tests/all/json_data.rs new file mode 100644 index 0000000..6d6165f --- /dev/null +++ b/tests/all/json_data.rs @@ -0,0 +1,24 @@ +//! Test with JSON blobs. +#![cfg(feature = "std")] + +use ::brief::value::Value; + +fn roundtrip(value: Value<'_>) { + let bytes = brief::to_vec(&value).expect("serializing"); + let parsed: Value<'_> = brief::from_slice(&bytes).expect("deserializing"); + assert_eq!(parsed, value); +} + +#[test] +fn test_json_blobs() { + for entry in std::fs::read_dir("./tests/data").expect("finding test data") { + let entry = entry.expect("getting directory entry"); + let file = entry.path(); + if file.ends_with(".json") { + println!("Testing `{}`", file.display()); + let json = std::fs::read_to_string(file).expect("reading JSON file"); + let value: Value = serde_json::from_str(&json).expect("parsing JSON"); + roundtrip(value); + } + } +} diff --git a/tests/all/main.rs b/tests/all/main.rs new file mode 100644 index 0000000..aa7e28f --- /dev/null +++ b/tests/all/main.rs @@ -0,0 +1,4 @@ +//! All integration tests go in this folder to speed up compilation. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::print_stdout, reason = "Tests")] + +mod json_data; diff --git a/tests/data/json-edge-cases.json b/tests/data/json-edge-cases.json new file mode 100644 index 0000000..d2e2453 --- /dev/null +++ b/tests/data/json-edge-cases.json @@ -0,0 +1,226 @@ +{ + "resourceType": "Patient", + "identifier": [ + { + "period": { + "start": "2001-05-06" + }, + "assigner": { + "display": "Acme Healthcare" + }, + "use": "usual", + "system": "urn:oid:1.2.36.146.595.217.0.1", + "value": "12345" + } + ], + "managingOrganization": { + "reference": "Organization/1" + }, + "_active": { + "extension": [ + { + "url": "http://example.org/fhir/StructureDefinition/recordStatus", + "valueCode": "archived" + } + ] + }, + "name": [ + { + "given": [ + "Peter", + "James" + ], + "use": "official", + "family": "Chalmers" + }, + { + "given": [ + "Jim" + ], + "use": "usual" + } + ], + "extension": [ + { + "url": "http://example.org/fhir/StructureDefinition/patientAvatar", + "valueReference": { + "reference": "#pic1", + "display": "Duck image" + } + }, + { + "url": "http://example.org/fhir/StructureDefinition/complexExtensionExample", + "extension": [ + { + "url": "nestedA", + "valueCoding": { + "system": "http://demo.org/id/4", + "code": "AB45", + "extension": [ + { + "url": "http://example.org/fhir/StructureDefinition/extraforcodingWithExt", + "extension": [ + { + "url": "extra1", + "valueString": "extra info" + } + ] + }, + { + "url": "http://example.org/fhir/StructureDefinition/extraforcodingWithValue", + "valueInteger": 45 + } + ] + } + }, + { + "url": "nestedB", + "id": "q4", + "extension": [ + { + "url": "nestedB1", + "valueString": "hello" + } + ] + } + ] + } + ], + "modifierExtension": [ + { + "url": "http://example.org/fhir/StructureDefinition/pi", + "valueDecimal": 3.141592653589793 + }, + { + "url": "http://example.org/fhir/StructureDefinition/max-decimal-precision", + "valueDecimal": 1.00065022141624642 + } + ], + "gender": "male", + "birthDate": "1974-12", + "deceasedBoolean": true, + "address": [ + { + "use": "home", + "line": [ + "534 Erewhon St" + ], + "city": "PleasantVille", + "state": "Vic", + "postalCode": "3999" + } + ], + "maritalStatus": { + "extension": [ + { + "url": "http://example.org/fhir/StructureDefinition/nullFlavor", + "valueCode": "ASKU" + } + ] + }, + "multipleBirthInteger": 3, + "text": { + "status": "generated", + "div": "\u003cdiv xmlns\u003d\"http://www.w3.org/1999/xhtml\"\u003e\n \u003ctable\u003e\n \u003ctbody\u003e\n \u003ctr\u003e\n \u003ctd\u003eName\u003c/td\u003e\n \u003ctd\u003ePeter James \u003cb\u003eChalmers\u003c/b\u003e (\u0026quot;Jim\u0026quot;)\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eAddress\u003c/td\u003e\n \u003ctd\u003e534 Erewhon, Pleasantville, Vic, 3999\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eContacts\u003c/td\u003e\n \u003ctd\u003eHome: unknown. Work: (03) 5555 6473\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eId\u003c/td\u003e\n \u003ctd\u003eMRN: 12345 (Acme Healthcare)\u003c/td\u003e\n \u003c/tr\u003e\n \u003c/tbody\u003e\n \u003c/table\u003e\n \u003c/div\u003e" + }, + "contained": [ + { + "resourceType": "Binary", + "id": "pic1", + "contentType": "image/gif", + "data": "R0lGODlhEwARAPcAAAAAAAAA/+9aAO+1AP/WAP/eAP/eCP/eEP/eGP/nAP/nCP/nEP/nIf/nKf/nUv/nWv/vAP/vCP/vEP/vGP/vIf/vKf/vMf/vOf/vWv/vY//va//vjP/3c//3lP/3nP//tf//vf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAEALAAAAAATABEAAAi+AAMIDDCgYMGBCBMSvMCQ4QCFCQcwDBGCA4cLDyEGECDxAoAQHjxwyKhQAMeGIUOSJJjRpIAGDS5wCDly4AALFlYOgHlBwwOSNydM0AmzwYGjBi8IHWoTgQYORg8QIGDAwAKhESI8HIDgwQaRDI1WXXAhK9MBBzZ8/XDxQoUFZC9IiCBh6wEHGz6IbNuwQoSpWxEgyLCXL8O/gAnylNlW6AUEBRIL7Og3KwQIiCXb9HsZQoIEUzUjNEiaNMKAAAA7" + }, + { + "resourceType": "Organization", + "id": "org3141", + "text": { + "status": "generated", + "div": "\u003cdiv xmlns\u003d\"http://www.w3.org/1999/xhtml\"\u003e\n \u003cp\u003eGood Health Clinic\u003c/p\u003e\n \u003c/div\u003e" + }, + "identifier": [ + { + "system": "urn:ietf:rfc:3986", + "value": "2.16.840.1.113883.19.5" + } + ], + "name": "Good Health Clinic" + } + ], + "contact": [ + { + "name": { + "family": "du Marché", + "_family": { + "extension": [ + { + "url": "http://example.org/fhir/StructureDefinition/qualifier", + "valueString": "VV" + }, + { + "url": "http://hl7.org/fhir/StructureDefinitioniso-21090#nullFlavor", + "valueCode": "ASKU" + } + ] + }, + "_given": [ + null, + { + "id": "a3", + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/qualifier", + "valueCode": "MID" + } + ] + }, + null + ], + "given": [ + "Bénédicte", + "Denise", + "Marie" + ] + }, + "relationship": [ + { + "coding": [ + { + "system": "http://example.org/fhir/CodeSystem/patient-contact-relationship", + "code": "partner" + } + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "+33 (237) 998327" + } + ] + } + ], + "generalPractitioner": [ + { + "reference": "#org3141" + } + ], + "telecom": [ + { + "use": "home" + }, + { + "system": "phone", + "value": "(03) 5555 6473", + "use": "work" + } + ], + "meta": { + "tag": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActReason", + "code": "HTEST", + "display": "test health data" + } + ] + } +} \ No newline at end of file