diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87ca19b4..e166236f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,8 @@ jobs: features: rayon - rust: stable features: serde + - rust: stable + features: sval - rust: stable features: borsh - rust: stable @@ -62,6 +64,10 @@ jobs: if: matrix.features == 'serde' run: | cargo test --verbose -p test-serde + - name: Tests (sval) + if: matrix.features == 'sval' + run: | + cargo test --verbose -p test-sval - name: Test run benchmarks if: matrix.bench != '' run: cargo test -v --benches @@ -141,7 +147,7 @@ jobs: - name: Build (nightly) run: cargo +nightly build --verbose --all-features - name: Build (MSRV) - run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon + run: cargo build --verbose --features arbitrary,quickcheck,serde,sval,rayon # One job that "summarizes" the success state of this pipeline. This can then be added to branch # protection, rather than having to add each job separately. diff --git a/Cargo.toml b/Cargo.toml index f7fa6c5e..c00e1a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } serde = { version = "1.0", optional = true, default-features = false } rayon = { version = "1.9", optional = true } +sval = { version = "2", optional = true, default-features = false } # deprecated: use borsh's "indexmap" feature instead. borsh = { version = "1.2", optional = true, default-features = false } @@ -51,11 +52,11 @@ sign-tag = true tag-name = "{{version}}" [package.metadata.docs.rs] -features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"] +features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon", "sval"] rustdoc-args = ["--cfg", "docsrs"] [workspace] -members = ["test-nostd", "test-serde"] +members = ["test-nostd", "test-serde", "test-sval"] [lints.clippy] style = "allow" diff --git a/src/lib.rs b/src/lib.rs index 04995a27..1aa7cbab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,8 @@ mod macros; mod borsh; #[cfg(feature = "serde")] mod serde; +#[cfg(feature = "sval")] +mod sval; mod util; pub mod map; diff --git a/src/sval.rs b/src/sval.rs new file mode 100644 index 00000000..73f096cf --- /dev/null +++ b/src/sval.rs @@ -0,0 +1,36 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "sval")))] + +use crate::{IndexMap, IndexSet}; +use sval::{Stream, Value}; + +impl Value for IndexMap { + fn stream<'sval, ST: Stream<'sval> + ?Sized>(&'sval self, stream: &mut ST) -> sval::Result { + stream.map_begin(Some(self.len()))?; + + for (k, v) in self { + stream.map_key_begin()?; + stream.value(k)?; + stream.map_key_end()?; + + stream.map_value_begin()?; + stream.value(v)?; + stream.map_value_end()?; + } + + stream.map_end() + } +} + +impl Value for IndexSet { + fn stream<'sval, ST: Stream<'sval> + ?Sized>(&'sval self, stream: &mut ST) -> sval::Result { + stream.seq_begin(Some(self.len()))?; + + for value in self { + stream.seq_value_begin()?; + stream.value(value)?; + stream.seq_value_end()?; + } + + stream.seq_end() + } +} diff --git a/test-sval/Cargo.toml b/test-sval/Cargo.toml new file mode 100644 index 00000000..a363f984 --- /dev/null +++ b/test-sval/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test-sval" +version = "0.1.0" +publish = false +edition = "2021" + +[dependencies] + +[dev-dependencies] +fnv = "1.0" +indexmap = { path = "..", features = ["sval"] } +sval = { version = "2", features = ["derive"] } +sval_test = "2" diff --git a/test-sval/src/lib.rs b/test-sval/src/lib.rs new file mode 100644 index 00000000..41531aad --- /dev/null +++ b/test-sval/src/lib.rs @@ -0,0 +1,104 @@ +#![cfg(test)] + +use fnv::FnvBuildHasher; +use indexmap::{indexmap, indexset, IndexMap, IndexSet}; +use sval_test::{assert_tokens, Token}; + +#[test] +fn test_sval_map() { + let map = indexmap! { 1 => 2, 3 => 4 }; + assert_tokens( + &map, + &[ + Token::MapBegin(Some(2)), + Token::MapKeyBegin, + Token::I32(1), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(2), + Token::MapValueEnd, + Token::MapKeyBegin, + Token::I32(3), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(4), + Token::MapValueEnd, + Token::MapEnd, + ], + ); +} + +#[test] +fn test_sval_set() { + let set = indexset! { 1, 2, 3, 4 }; + assert_tokens( + &set, + &[ + Token::SeqBegin(Some(4)), + Token::SeqValueBegin, + Token::I32(1), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(2), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(3), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(4), + Token::SeqValueEnd, + Token::SeqEnd, + ], + ); +} + +#[test] +fn test_sval_map_fnv_hasher() { + let mut map: IndexMap = Default::default(); + map.insert(1, 2); + map.insert(3, 4); + assert_tokens( + &map, + &[ + Token::MapBegin(Some(2)), + Token::MapKeyBegin, + Token::I32(1), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(2), + Token::MapValueEnd, + Token::MapKeyBegin, + Token::I32(3), + Token::MapKeyEnd, + Token::MapValueBegin, + Token::I32(4), + Token::MapValueEnd, + Token::MapEnd, + ], + ); +} + +#[test] +fn test_sval_set_fnv_hasher() { + let mut set: IndexSet = Default::default(); + set.extend(1..5); + assert_tokens( + &set, + &[ + Token::SeqBegin(Some(4)), + Token::SeqValueBegin, + Token::I32(1), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(2), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(3), + Token::SeqValueEnd, + Token::SeqValueBegin, + Token::I32(4), + Token::SeqValueEnd, + Token::SeqEnd, + ], + ); +}