diff --git a/.gitignore b/.gitignore index ec061ab..a474c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ lcov.info /target /Cargo.lock +dodo.py +.doit.db* +__pycache__ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 85ba945..4447b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ keywords = ["CosmWasm"] [workspace.dependencies] storey = { path = "packages/storey", version = "0.3" } storey-encoding = { path = "packages/storey-encoding", version = "0.1" } +storey-macros = { path = "packages/storey-macros", version = "0.1" } storey-storage = { path = "packages/storey-storage", version = "0.1" } diff --git a/packages/cw-storey/Cargo.toml b/packages/cw-storey/Cargo.toml index 93979aa..6e9819e 100644 --- a/packages/cw-storey/Cargo.toml +++ b/packages/cw-storey/Cargo.toml @@ -16,3 +16,6 @@ rmp-serde = "1.1" serde = "1" storey = { workspace = true } + +[dev-dependencies] +mocks = { path = "../mocks" } diff --git a/packages/cw-storey/src/backend.rs b/packages/cw-storey/src/backend.rs index 60594cd..9729be1 100644 --- a/packages/cw-storey/src/backend.rs +++ b/packages/cw-storey/src/backend.rs @@ -5,6 +5,7 @@ use storey::storage::{ }; /// A wrapper around a type implementing [`cosmwasm_std::Storage`] that integrates it with [`storey`]. +#[repr(transparent)] pub struct CwStorage(pub S); impl StorageBackend for CwStorage @@ -33,9 +34,18 @@ impl IterableStorage for CwStorage where S: cosmwasm_std::Storage + ?Sized, { - type KeysIterator<'a> = Box> + 'a> where Self: 'a; - type ValuesIterator<'a> = Box> + 'a> where Self: 'a; - type PairsIterator<'a> = Box, Vec)> + 'a> where Self: 'a; + type KeysIterator<'a> + = Box> + 'a> + where + Self: 'a; + type ValuesIterator<'a> + = Box> + 'a> + where + Self: 'a; + type PairsIterator<'a> + = Box, Vec)> + 'a> + where + Self: 'a; fn keys<'a>(&'a self, start: Bound<&[u8]>, end: Bound<&[u8]>) -> Self::KeysIterator<'a> { let (start, end) = bounds_to_option(start, end); @@ -72,9 +82,18 @@ impl RevIterableStorage for CwStorage where S: cosmwasm_std::Storage + ?Sized, { - type RevKeysIterator<'a> = Box> + 'a> where Self: 'a; - type RevValuesIterator<'a> = Box> + 'a> where Self: 'a; - type RevPairsIterator<'a> = Box, Vec)> + 'a> where Self: 'a; + type RevKeysIterator<'a> + = Box> + 'a> + where + Self: 'a; + type RevValuesIterator<'a> + = Box> + 'a> + where + Self: 'a; + type RevPairsIterator<'a> + = Box, Vec)> + 'a> + where + Self: 'a; fn rev_keys<'a>(&'a self, start: Bound<&[u8]>, end: Bound<&[u8]>) -> Self::RevKeysIterator<'a> { let (start, end) = bounds_to_option(start, end); diff --git a/packages/cw-storey/src/containers.rs b/packages/cw-storey/src/containers.rs deleted file mode 100644 index 7bf97f4..0000000 --- a/packages/cw-storey/src/containers.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Storage containers for use with [*CosmWasm*] smart contracts. -//! -//! [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm - -/// The [`storey::containers::Item`] type with the default encoding for [*CosmWasm*] smart -/// contracts. -/// -/// [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm -pub type Item = storey::containers::Item; - -/// The [`storey::containers::Column`] type with the default encoding for [*CosmWasm*] smart -/// contracts. -/// -/// [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm -pub type Column = storey::containers::Column; - -pub use storey::containers::Map; diff --git a/packages/cw-storey/src/containers/key_set.rs b/packages/cw-storey/src/containers/key_set.rs new file mode 100644 index 0000000..b308469 --- /dev/null +++ b/packages/cw-storey/src/containers/key_set.rs @@ -0,0 +1,204 @@ +use cosmwasm_std::{Addr, Int128, Int256, Int512, Int64, Uint128, Uint256, Uint512, Uint64}; +use storey::containers::map::key::{ + DynamicKey, FixedSizeKey, KeySetDefaults, NumericKeyDecodeError, +}; +use storey::containers::map::{Key, OwnedKey}; + +/// The CosmWasm key set for use with storey's [`Map`](storey::containers::Map). +/// +/// This key set includes the usual standard library types (like `u32` or `String`) as well as `cosmwasm_std` types (like `Addr` and `Uint128`). +/// +/// For more information about key sets, take a look at the [`storey::containers::map::Key`] trait. +#[derive(KeySetDefaults)] +pub struct CwKeySet; + +impl Key for Addr { + type Kind = DynamicKey; + + fn encode(&self) -> Vec { + self.as_str().as_bytes().to_vec() + } +} + +impl OwnedKey for Addr { + type Error = ::Error; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + ::from_bytes(bytes).map(Addr::unchecked) + } +} + +macro_rules! cosmwasm_std_uints1 { + ($($ty:ty => $size:expr, $stdty:ty),*) => { + $( + impl Key for $ty { + type Kind = FixedSizeKey<$size>; + + fn encode(&self) -> Vec { + self.to_be_bytes().to_vec() + } + } + + impl OwnedKey for $ty { + type Error = NumericKeyDecodeError; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + let array: [u8; $size] = bytes.try_into().map_err(|_| NumericKeyDecodeError::InvalidLength)?; + Ok(<$stdty>::from_be_bytes(array).into()) + } + } + )* + } +} + +cosmwasm_std_uints1!( + Uint64 => 8, u64, + Uint128 => 16, u128 +); + +macro_rules! cosmwasm_std_uints2 { + ($($ty:ty => $size:expr),*) => { + $( + impl Key for $ty { + type Kind = FixedSizeKey<$size>; + + fn encode(&self) -> Vec { + self.to_be_bytes().to_vec() + } + } + + impl OwnedKey for $ty { + type Error = NumericKeyDecodeError; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + let array: [u8; $size] = bytes.try_into().map_err(|_| NumericKeyDecodeError::InvalidLength)?; + Ok(<$ty>::from_be_bytes(array)) + } + } + )* + } +} + +cosmwasm_std_uints2!( + Uint256 => 32, + Uint512 => 64 +); + +macro_rules! cosmwasm_std_ints { + ($($ty:ty => $size:expr),*) => { + $( + impl Key for $ty { + type Kind = FixedSizeKey<$size>; + + fn encode(&self) -> Vec { + let mut bytes = self.to_be_bytes(); + bytes[0] ^= 0x80; + + bytes.to_vec() + } + } + + impl OwnedKey for $ty { + type Error = NumericKeyDecodeError; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + let mut array: [u8; $size] = bytes.try_into().map_err(|_| NumericKeyDecodeError::InvalidLength)?; + array[0] ^= 0x80; + + Ok(<$ty>::from_be_bytes(array)) + } + } + )* + } +} + +cosmwasm_std_ints!(Int64 => 8, Int128 => 16, Int256 => 32, Int512 => 64); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unsigned_ints_1() { + let test_vector = [ + (Uint64::from(0u64), [0, 0, 0, 0, 0, 0, 0, 0]), + (Uint64::from(1u64), [0, 0, 0, 0, 0, 0, 0, 1]), + ( + Uint64::from(0x1234567890abcdefu64), + [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef], + ), + ]; + + for (num, expected) in test_vector.iter() { + let encoded = num.encode(); + assert_eq!(encoded, *expected); + } + + for (expected, bytes) in test_vector.iter() { + let decoded = Uint64::from_bytes(bytes).unwrap(); + assert_eq!(decoded, *expected); + } + } + + #[test] + fn unsigned_ints_2() { + let test_vector = [ + (Uint256::from(0u64), [0; 32]), + ( + Uint256::new([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, + 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, + 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + ]), + [ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, + 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, + 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + ], + ), + ]; + + for (num, expected) in test_vector.iter() { + let encoded = num.encode(); + assert_eq!(encoded, *expected); + } + + for (expected, bytes) in test_vector.iter() { + let decoded = Uint256::from_bytes(bytes).unwrap(); + assert_eq!(decoded, *expected); + } + } + + #[test] + fn signed_ints() { + let nums = [ + Int256::from(-542), + Int256::from(-111), + Int256::from(0), + Int256::from(121), + Int256::from(342), + ]; + + let mut byte_nums = nums.iter().map(|n| n.encode()).collect::>(); + byte_nums.sort(); + + let result = byte_nums + .iter() + .map(|bytes| Int256::from_bytes(bytes).unwrap()) + .collect::>(); + + assert_eq!(result, nums); + } +} diff --git a/packages/cw-storey/src/containers/mod.rs b/packages/cw-storey/src/containers/mod.rs new file mode 100644 index 0000000..2c93b2a --- /dev/null +++ b/packages/cw-storey/src/containers/mod.rs @@ -0,0 +1,43 @@ +//! Storage containers for use with [*CosmWasm*] smart contracts. +//! +//! [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm + +mod key_set; + +pub use key_set::CwKeySet; + +/// The [`storey::containers::Item`] type with the default encoding for [*CosmWasm*] smart +/// contracts. +/// +/// [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm +pub type Item = storey::containers::Item; + +/// The [`storey::containers::Column`] type with the default encoding for [*CosmWasm*] smart +/// contracts. +/// +/// [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm +pub type Column = storey::containers::Column; + +/// The [`storey::containers::Map`] type with the [`CwKeySet`] key set, which includes the +/// usual standard library types (like `u32` or `String`) as well as `cosmwasm_std` types (like `Addr` and `Uint128`). +pub type Map = storey::containers::Map; + +#[cfg(test)] +mod tests { + use super::*; + + use cosmwasm_std::Addr; + use mocks::backend::TestStorage; + + #[test] + fn map_addr() { + let map: Map> = Map::new(0); + let mut storage = TestStorage::new(); + + let key = Addr::unchecked("addr1"); + + map.access(&mut storage).entry_mut(&key).set(&42).unwrap(); + + assert_eq!(map.access(&storage).entry(&key).get().unwrap(), Some(42)); + } +} diff --git a/packages/storey-macros/Cargo.toml b/packages/storey-macros/Cargo.toml new file mode 100644 index 0000000..4989101 --- /dev/null +++ b/packages/storey-macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "storey-macros" +version = "0.1.0" +edition = "2021" +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +proc-macro-error = "1" +syn = { version = "2", features = ["full"] } +quote = "1" diff --git a/packages/storey-macros/src/key_set.rs b/packages/storey-macros/src/key_set.rs new file mode 100644 index 0000000..a03fbc3 --- /dev/null +++ b/packages/storey-macros/src/key_set.rs @@ -0,0 +1,103 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, Ident, ItemStruct}; + +pub fn derive(input: ItemStruct) -> Result { + let name = &input.ident; + + let key_impls = derive_key_impls(name); + let owned_key_impls = derive_owned_key_impls(name); + let arrays = derive_arrays(name); + + Ok(quote! { + #key_impls + #owned_key_impls + #arrays + }) +} + +pub fn derive_key_impls(name: &Ident) -> TokenStream { + let mut types = get_owned_delegations(); + types.extend(get_key_only_delegations()); + + quote! { + #( + impl Key<#name> for #types { + type Kind = <#types as Key>::Kind; + + fn encode(&self) -> Vec { + <#types as Key>::encode(self) + } + } + )* + } +} + +pub fn derive_owned_key_impls(name: &Ident) -> TokenStream { + let types = get_owned_delegations(); + + quote! { + #( + impl OwnedKey<#name> for #types { + type Error = <#types as OwnedKey>::Error; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + <#types as OwnedKey>::from_bytes(bytes) + } + } + )* + } +} + +pub fn derive_arrays(name: &Ident) -> TokenStream { + quote::quote! { + impl Key<#name> for [u8; N] { + type Kind = ::Kind; + + fn encode(&self) -> Vec { + ::encode(self) + } + } + + impl OwnedKey<#name> for [u8; N] { + type Error = ::Error; + + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + ::from_bytes(bytes) + } + } + } +} + +fn get_owned_delegations() -> Vec { + // these implement both Key and OwnedKey + + vec![ + parse_quote!(String), + parse_quote!(Box), + parse_quote!(Vec), + parse_quote!(Box<[u8]>), + parse_quote!(u8), + parse_quote!(u16), + parse_quote!(u32), + parse_quote!(u64), + parse_quote!(u128), + parse_quote!(i8), + parse_quote!(i16), + parse_quote!(i32), + parse_quote!(i64), + parse_quote!(i128), + ] +} + +fn get_key_only_delegations() -> Vec { + // these implement Key but not OwnedKey + + vec![parse_quote!(str), parse_quote!([u8])] +} diff --git a/packages/storey-macros/src/lib.rs b/packages/storey-macros/src/lib.rs new file mode 100644 index 0000000..ee6b9bd --- /dev/null +++ b/packages/storey-macros/src/lib.rs @@ -0,0 +1,13 @@ +mod key_set; + +#[proc_macro_derive(KeySetDefaults)] +pub fn key_set_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::ItemStruct); + + let expanded = match key_set::derive(input) { + Ok(res) => res, + Err(e) => e.into_compile_error(), + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/packages/storey/Cargo.toml b/packages/storey/Cargo.toml index 87c2055..8f41281 100644 --- a/packages/storey/Cargo.toml +++ b/packages/storey/Cargo.toml @@ -16,6 +16,7 @@ keywords.workspace = true thiserror = "1" storey-encoding.workspace = true +storey-macros.workspace = true storey-storage.workspace = true [dev-dependencies] diff --git a/packages/storey/src/containers/column.rs b/packages/storey/src/containers/column.rs index 2049a7a..5041660 100644 --- a/packages/storey/src/containers/column.rs +++ b/packages/storey/src/containers/column.rs @@ -9,7 +9,9 @@ use crate::storage::{IterableStorage, StorageBranch}; use crate::storage::{Storage, StorageMut}; use super::common::TryGetError; -use super::{BoundFor, BoundedIterableAccessor, IterableAccessor, NonTerminal, Storable}; +use super::{ + BoundFor, BoundedIterableAccessor, IterableAccessor, IterableStorable, NonTerminal, Storable, +}; /// The first (lowest) ID that is pushed to the column. const FIRST_ID: u32 = 1; @@ -97,25 +99,16 @@ where } } -impl Storable for Column +impl IterableStorable for Column where E: Encoding, T: EncodableWith + DecodableWith, { - type Kind = NonTerminal; - type Accessor = ColumnAccess; type Key = u32; type KeyDecodeError = ColumnIdDecodeError; type Value = T; type ValueDecodeError = E::DecodeError; - fn access_impl(storage: S) -> ColumnAccess { - ColumnAccess { - storage, - phantom: PhantomData, - } - } - fn decode_key(key: &[u8]) -> Result { let key = decode_id(key)?; @@ -127,6 +120,22 @@ where } } +impl Storable for Column +where + E: Encoding, + T: EncodableWith + DecodableWith, +{ + type Kind = NonTerminal; + type Accessor = ColumnAccess; + + fn access_impl(storage: S) -> ColumnAccess { + ColumnAccess { + storage, + phantom: PhantomData, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, thiserror::Error)] #[error("invalid key length, expected 4 bytes of big-endian u32")] pub struct ColumnIdDecodeError; diff --git a/packages/storey/src/containers/item.rs b/packages/storey/src/containers/item.rs index a936c01..78d5d2a 100644 --- a/packages/storey/src/containers/item.rs +++ b/packages/storey/src/containers/item.rs @@ -7,7 +7,7 @@ use crate::storage::StorageBranch; use crate::storage::{Storage, StorageMut}; use super::common::TryGetError; -use super::{Storable, Terminal}; +use super::{IterableStorable, Storable, Terminal}; /// A single item in the storage. /// @@ -80,10 +80,6 @@ where { type Kind = Terminal; type Accessor = ItemAccess; - type Key = (); - type KeyDecodeError = ItemKeyDecodeError; - type Value = T; - type ValueDecodeError = E::DecodeError; fn access_impl(storage: S) -> ItemAccess { ItemAccess { @@ -91,6 +87,17 @@ where phantom: PhantomData, } } +} + +impl IterableStorable for Item +where + E: Encoding, + T: EncodableWith + DecodableWith, +{ + type Key = (); + type KeyDecodeError = ItemKeyDecodeError; + type Value = T; + type ValueDecodeError = E::DecodeError; fn decode_key(key: &[u8]) -> Result<(), ItemKeyDecodeError> { if key.is_empty() { @@ -100,7 +107,7 @@ where } } - fn decode_value(value: &[u8]) -> Result { + fn decode_value(value: &[u8]) -> Result { T::decode(value) } } diff --git a/packages/storey/src/containers/map/key.rs b/packages/storey/src/containers/map/key/impls.rs similarity index 65% rename from packages/storey/src/containers/map/key.rs rename to packages/storey/src/containers/map/key/impls.rs index d4de44d..7489645 100644 --- a/packages/storey/src/containers/map/key.rs +++ b/packages/storey/src/containers/map/key/impls.rs @@ -1,22 +1,6 @@ -/// A key that can be used with a [`Map`](super::Map). -pub trait Key { - /// The kind of key, meaning either fixed size or dynamic size. - type Kind: KeyKind; +//! Implementations of the `Key`/`OwnedKey` trait for Rust std types. - /// Encode the key into a byte vector. - fn encode(&self) -> Vec; -} - -/// An owned key that can be used with a [`Map`](super::Map). -pub trait OwnedKey: Key { - /// The error type that can occur when decoding the key. - type Error; - - /// Decode the key from a byte slice. - fn from_bytes(bytes: &[u8]) -> Result - where - Self: Sized; -} +use super::{DynamicKey, FixedSizeKey, Key, OwnedKey}; impl Key for String { type Kind = DynamicKey; @@ -155,31 +139,6 @@ impl OwnedKey for [u8; N] { } } -/// A trait specifying the kind of key. -/// -/// There are two kinds of keys: fixed-size keys and dynamic keys, which are -/// represented by the [`FixedSizeKey`] and [`DynamicKey`] types, respectively. -/// -/// This trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits) -/// and cannot be implemented outside of this crate. -pub trait KeyKind: sealed::KeyKindSeal {} - -/// A marker type representing a fixed-size key. -pub struct FixedSizeKey; - -/// A marker type representing a dynamic-size key. -pub struct DynamicKey; - -impl KeyKind for FixedSizeKey {} -impl KeyKind for DynamicKey {} - -mod sealed { - pub trait KeyKindSeal {} - - impl KeyKindSeal for super::FixedSizeKey {} - impl KeyKindSeal for super::DynamicKey {} -} - /// An error type for decoding numeric keys. #[derive(Debug, PartialEq, Eq, Clone, Copy, thiserror::Error)] pub enum NumericKeyDecodeError { @@ -254,39 +213,3 @@ macro_rules! impl_key_for_signed { } impl_key_for_signed!(i8 : u8, i16 : u16, i32 : u32, i64 : u64, i128 : u128); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn signed_int_ordering() { - let data = [-555555555, -3333, -1, 0, 1, 3333, 55555555]; - - let mut encoded = data.iter().map(|&x| x.encode()).collect::>(); - encoded.sort(); - - let decoded = encoded - .iter() - .map(|x| i32::from_bytes(x).unwrap()) - .collect::>(); - - assert_eq!(&data[..], &decoded); - } - - #[test] - fn signed_int_encoding() { - // negative values have the leftmost bit unset - assert_eq!((i32::MIN).encode(), [0b00000000, 0x00, 0x00, 0x00]); - assert_eq!((-2000i32).encode(), [0b01111111, 0xff, 248, 48]); - assert_eq!((-3i32).encode(), [0b01111111, 0xff, 0xff, 0xfd]); - assert_eq!((-2i32).encode(), [0b01111111, 0xff, 0xff, 0xfe]); - assert_eq!((-1i32).encode(), [0b01111111, 0xff, 0xff, 0xff]); - - // non-negative values are BE encoded, but with the leftmost bit set - assert_eq!(0i32.encode(), [0b10000000, 0x00, 0x00, 0x00]); - assert_eq!(1i32.encode(), [0b10000000, 0x00, 0x00, 0x01]); - assert_eq!(2i32.encode(), [0b10000000, 0x00, 0x00, 0x02]); - assert_eq!(i32::MAX.encode(), [0b11111111, 0xff, 0xff, 0xff]); - } -} diff --git a/packages/storey/src/containers/map/key/key_set.rs b/packages/storey/src/containers/map/key/key_set.rs new file mode 100644 index 0000000..d4e730a --- /dev/null +++ b/packages/storey/src/containers/map/key/key_set.rs @@ -0,0 +1,4 @@ +/// The default key set for use with a [`Map`](super::super::Map). +/// +/// To find out more about key sets, take a look at the [`Key`](super::Key) trait's documentation. +pub struct DefaultKeySet; diff --git a/packages/storey/src/containers/map/key/kind.rs b/packages/storey/src/containers/map/key/kind.rs new file mode 100644 index 0000000..8df23b1 --- /dev/null +++ b/packages/storey/src/containers/map/key/kind.rs @@ -0,0 +1,24 @@ +/// A trait specifying the kind of key. +/// +/// There are two kinds of keys: fixed-size keys and dynamic keys, which are +/// represented by the [`FixedSizeKey`] and [`DynamicKey`] types, respectively. +/// +/// This trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits) +/// and cannot be implemented outside of this crate. +pub trait KeyKind: sealed::KeyKindSeal {} + +/// A marker type representing a fixed-size key. +pub struct FixedSizeKey; + +/// A marker type representing a dynamic-size key. +pub struct DynamicKey; + +impl KeyKind for FixedSizeKey {} +impl KeyKind for DynamicKey {} + +mod sealed { + pub trait KeyKindSeal {} + + impl KeyKindSeal for super::FixedSizeKey {} + impl KeyKindSeal for super::DynamicKey {} +} diff --git a/packages/storey/src/containers/map/key/mod.rs b/packages/storey/src/containers/map/key/mod.rs new file mode 100644 index 0000000..5beb45b --- /dev/null +++ b/packages/storey/src/containers/map/key/mod.rs @@ -0,0 +1,141 @@ +mod impls; +mod key_set; +mod kind; + +pub use impls::{ArrayDecodeError, InvalidUtf8, NumericKeyDecodeError}; +pub use key_set::DefaultKeySet; +pub use kind::{DynamicKey, FixedSizeKey, KeyKind}; + +/// When applied to a type `KS`, this derive macro generates implementations of the [`Key`] +/// and [`OwnedKey`] traits for appropriate Rust std types. This mirrors the implementations +/// that exist for [`Key`](Key#foreign-impls) and [`OwnedKey`](OwnedKey#foreign-impls). +/// +/// The purpose is to allow third-party crates to define their own key set types without +/// reinventing the wheel for the standard library types. +/// +/// More about key sets can be found in the [`Key`] trait documentation. +pub use storey_macros::KeySetDefaults; + +/// A key that can be used with a [`Map`](super::Map). +/// +/// # Key sets +/// +/// The `KS` type parameter is the "key set" used. This is a marker type that +/// specifies the kind of keys that can be used with the map. The default key +/// set is [`DefaultKeySet`]. Providing another key set is an extension mechanism - +/// third party crates can define their own key set types to support third-party key types, +/// getting around orphan rules. +/// +/// # Examples +/// +/// This example shows how to define an alternative key set type. To use it with a map, +/// the map also needs to be parameterized with the key set type; +/// +/// ``` +/// use storey::containers::map::{Key, OwnedKey}; +/// use storey::containers::map::key::{DynamicKey, FixedSizeKey}; +/// +/// pub struct MyKeySet; +/// +/// // imagine this is a third-party type +/// pub struct ExtType; +/// +/// impl Key for String { +/// type Kind = DynamicKey; +/// +/// fn encode(&self) -> Vec { +/// self.as_bytes().to_vec() +/// } +/// } +/// +/// impl OwnedKey for String { +/// type Error = std::string::FromUtf8Error; +/// +/// fn from_bytes(bytes: &[u8]) -> Result { +/// String::from_utf8(bytes.to_vec()) +/// } +/// } +/// +/// impl Key for ExtType { +/// type Kind = FixedSizeKey<16>; +/// +/// fn encode(&self) -> Vec { +/// todo!() +/// } +/// } +/// +/// impl OwnedKey for ExtType { +/// type Error = (); +/// +/// fn from_bytes(bytes: &[u8]) -> Result { +/// todo!() +/// } +/// } +/// +/// // use the key set with a map +/// let map: storey::containers::map::Map = storey::containers::map::Map::new(0); +/// ``` +pub trait Key { + /// The kind of key, meaning either fixed size or dynamic size. + type Kind: KeyKind; + + /// Encode the key into a byte vector. + fn encode(&self) -> Vec; +} + +/// An owned key that can be used with a [`Map`](super::Map). +/// +/// # Key sets +/// +/// The `KS` type parameter is the "key set" used. This is a marker type that +/// specifies the kind of keys that can be used with the map. The default key +/// set is [`DefaultKeySet`]. Providing another key set is an extension mechanism - +/// third party crates can define their own key set types to support third-party key types, +/// without bumping into orphan rules. +/// +/// An example of a custom key set is shown in the [`Key`] trait documentation. +pub trait OwnedKey: Key { + /// The error type that can occur when decoding the key. + type Error; + + /// Decode the key from a byte slice. + fn from_bytes(bytes: &[u8]) -> Result + where + Self: Sized; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn signed_int_ordering() { + let data = [-555555555, -3333, -1, 0, 1, 3333, 55555555]; + + let mut encoded = data.iter().map(|&x| x.encode()).collect::>(); + encoded.sort(); + + let decoded = encoded + .iter() + .map(|x| i32::from_bytes(x).unwrap()) + .collect::>(); + + assert_eq!(&data[..], &decoded); + } + + #[test] + fn signed_int_encoding() { + // negative values have the leftmost bit unset + assert_eq!((i32::MIN).encode(), [0b00000000, 0x00, 0x00, 0x00]); + assert_eq!((-2000i32).encode(), [0b01111111, 0xff, 248, 48]); + assert_eq!((-3i32).encode(), [0b01111111, 0xff, 0xff, 0xfd]); + assert_eq!((-2i32).encode(), [0b01111111, 0xff, 0xff, 0xfe]); + assert_eq!((-1i32).encode(), [0b01111111, 0xff, 0xff, 0xff]); + + // non-negative values are BE encoded, but with the leftmost bit set + assert_eq!(0i32.encode(), [0b10000000, 0x00, 0x00, 0x00]); + assert_eq!(1i32.encode(), [0b10000000, 0x00, 0x00, 0x01]); + assert_eq!(2i32.encode(), [0b10000000, 0x00, 0x00, 0x02]); + assert_eq!(i32::MAX.encode(), [0b11111111, 0xff, 0xff, 0xff]); + } +} diff --git a/packages/storey/src/containers/map/mod.rs b/packages/storey/src/containers/map/mod.rs index 5574b23..b93b4c8 100644 --- a/packages/storey/src/containers/map/mod.rs +++ b/packages/storey/src/containers/map/mod.rs @@ -1,6 +1,7 @@ pub mod key; mod key_encoding; +use key::DefaultKeySet; pub use key::{Key, OwnedKey}; use key_encoding::KeyEncoding; use key_encoding::KeyEncodingT; @@ -15,6 +16,7 @@ use self::key::FixedSizeKey; use super::BoundFor; use super::BoundedIterableAccessor; use super::IterableAccessor; +use super::IterableStorable; use super::NonTerminal; use super::Storable; use super::Terminal; @@ -26,6 +28,16 @@ use super::Terminal; /// A map does not directly manage the storage of its values. Instead, it doles out access to /// a collection of other containers. /// +/// # Key sets +/// +/// The `KS` type parameter is the "key set" used. This is a marker type that +/// specifies the kind of keys that can be used with the map. The default key +/// set is [`DefaultKeySet`]. Providing another key set is an extension mechanism - +/// third party crates can define their own key set types to support third-party key types, +/// without bumping into orphan rules. +/// +/// An example of a custom key set implementation is shown in the [`Key`] trait documentation. +/// /// # Examples /// /// ``` @@ -55,18 +67,12 @@ use super::Terminal; /// assert_eq!(access.entry("foo").entry("bar").get().unwrap(), Some(1337)); /// assert_eq!(access.entry("foo").entry("baz").get().unwrap(), None); /// ``` -pub struct Map { +pub struct Map { prefix: u8, - phantom: PhantomData<(*const K, V)>, + phantom: PhantomData<(*const K, V, KS)>, } -impl Map -where - K: OwnedKey, - V: Storable, - ::KeyDecodeError: std::fmt::Display, - (K::Kind, V::Kind): KeyEncodingT, -{ +impl Map { /// Creates a new map with the given prefix. /// /// It is the responsibility of the caller to ensure that the prefix is unique and does not conflict @@ -98,7 +104,7 @@ where /// let map = Map::>::new(0); /// let mut access = map.access(&mut storage); /// ``` - pub fn access(&self, storage: F) -> MapAccess> + pub fn access(&self, storage: F) -> MapAccess, KS> where (F,): IntoStorage, { @@ -108,26 +114,29 @@ where } } -impl Storable for Map -where - K: OwnedKey, - V: Storable, - ::KeyDecodeError: std::fmt::Display, - (K::Kind, V::Kind): KeyEncodingT, -{ +impl Storable for Map { type Kind = NonTerminal; - type Accessor = MapAccess; - type Key = (K, V::Key); - type KeyDecodeError = MapKeyDecodeError; - type Value = V::Value; - type ValueDecodeError = V::ValueDecodeError; + type Accessor = MapAccess; - fn access_impl(storage: S) -> MapAccess { + fn access_impl(storage: S) -> MapAccess { MapAccess { storage, phantom: PhantomData, } } +} + +impl IterableStorable for Map +where + K: OwnedKey, + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, + (K::Kind, V::Kind): KeyEncodingT, +{ + type Key = (K, V::Key); + type KeyDecodeError = MapKeyDecodeError; + type Value = V::Value; + type ValueDecodeError = V::ValueDecodeError; fn decode_key(key: &[u8]) -> Result> { let behavior = <(K::Kind, V::Kind)>::BEHAVIOR; @@ -188,14 +197,14 @@ impl crate::error::StoreyError for MapKeyDecodeError {} /// An accessor for a map. /// /// The accessor provides methods for interacting with the map in storage. -pub struct MapAccess { +pub struct MapAccess { storage: S, - phantom: PhantomData<(*const K, V)>, + phantom: PhantomData<(*const K, V, KS)>, } -impl MapAccess +impl MapAccess where - K: Key, + K: Key, V: Storable, (K::Kind, V::Kind): KeyEncodingT, { @@ -229,7 +238,7 @@ where pub fn entry(&self, key: &Q) -> V::Accessor> where K: Borrow, - Q: Key + ?Sized, + Q: Key + ?Sized, { let behavior = <(K::Kind, V::Kind)>::BEHAVIOR; @@ -273,7 +282,7 @@ where pub fn entry_mut(&mut self, key: &Q) -> V::Accessor> where K: Borrow, - Q: Key + ?Sized, + Q: Key + ?Sized, { let behavior = <(K::Kind, V::Kind)>::BEHAVIOR; @@ -294,11 +303,11 @@ fn len_prefix>(bytes: T) -> Vec { result } -impl IterableAccessor for MapAccess +impl IterableAccessor for MapAccess where - K: OwnedKey, - V: Storable, - ::KeyDecodeError: std::fmt::Display, + K: OwnedKey, + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, S: IterableStorage, (K::Kind, V::Kind): KeyEncodingT, { @@ -317,11 +326,11 @@ where // after it, we have to length-prefix the key. This makes bounded iteration behave differently // than in other cases (and rather unintuitively). -impl BoundedIterableAccessor for MapAccess +impl BoundedIterableAccessor for MapAccess where - K: OwnedKey, - V: Storable, - ::KeyDecodeError: std::fmt::Display, + K: OwnedKey, + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, S: IterableStorage, (K::Kind, V::Kind): BoundedIterationAllowed + KeyEncodingT, { @@ -333,11 +342,11 @@ impl BoundedIterationAllowed for (FixedSizeKey, Terminal) {} impl BoundedIterationAllowed for (FixedSizeKey, NonTerminal) {} impl BoundedIterationAllowed for (DynamicKey, Terminal) {} -impl BoundFor> for &Q +impl BoundFor> for &Q where - K: Borrow + OwnedKey, + K: Borrow + OwnedKey, V: Storable, - Q: Key + ?Sized, + Q: Key + ?Sized, (K::Kind, V::Kind): KeyEncodingT, { fn into_bytes(self) -> Vec { diff --git a/packages/storey/src/containers/mod.rs b/packages/storey/src/containers/mod.rs index d3ddbc6..c44e63d 100644 --- a/packages/storey/src/containers/mod.rs +++ b/packages/storey/src/containers/mod.rs @@ -26,6 +26,13 @@ pub trait Storable { /// [`Storage`]: crate::storage::Storage type Accessor; + /// Create an accessor for this collection/container, given a [`Storage`] implementation. + /// + /// [`Storage`]: crate::storage::Storage + fn access_impl(storage: S) -> Self::Accessor; +} + +pub trait IterableStorable: Storable { /// The Key type for this collection/container. This is the type that will be used in /// key iteration. /// @@ -45,11 +52,6 @@ pub trait Storable { /// The error type for decoding values. type ValueDecodeError; - /// Create an accessor for this collection/container, given a [`Storage`] implementation. - /// - /// [`Storage`]: crate::storage::Storage - fn access_impl(storage: S) -> Self::Accessor; - /// Decode a key from a byte slice. /// /// This method is used in key iteration to provide a typed key rather than raw bytes @@ -363,7 +365,7 @@ pub struct StorableIter { impl Iterator for StorableIter where - S: Storable, + S: IterableStorable, I: Iterator, Vec)>, { type Item = Result<(S::Key, S::Value), KVDecodeError>; @@ -387,7 +389,7 @@ pub struct StorableKeys { impl Iterator for StorableKeys where - S: Storable, + S: IterableStorable, I: Iterator>, { type Item = Result; @@ -405,7 +407,7 @@ pub struct StorableValues { impl Iterator for StorableValues where - S: Storable, + S: IterableStorable, I: Iterator>, { type Item = Result; diff --git a/packages/storey/tests/key_set_defaults.rs b/packages/storey/tests/key_set_defaults.rs new file mode 100644 index 0000000..7971053 --- /dev/null +++ b/packages/storey/tests/key_set_defaults.rs @@ -0,0 +1,54 @@ +use storey::containers::map::key::{Key, KeySetDefaults, OwnedKey}; + +#[derive(KeySetDefaults)] +pub struct MyKeySet; + +#[test] +fn strings() { + let data = ["foo", "", "🧗🏼‍♀️hi"]; + + for s in data.iter() { + let encoded = ::encode(s); + let my_encoded = >::encode(s); + assert_eq!(encoded, my_encoded); + } + + for s in data.iter() { + let s = s.to_string(); + + let encoded = ::encode(&s); + let my_encoded = >::encode(&s); + assert_eq!(encoded, my_encoded); + + let my_decoded = + >::from_bytes(&encoded).unwrap(); + assert_eq!(s, my_decoded); + } + + for s in data.iter() { + let s: Box = (*s).into(); + + let encoded = as storey::containers::map::Key>::encode(&s); + let my_encoded = as storey::containers::map::Key>::encode(&s); + assert_eq!(encoded, my_encoded); + + let my_decoded = + as storey::containers::map::OwnedKey>::from_bytes(&encoded) + .unwrap(); + assert_eq!(s, my_decoded); + } +} + +#[test] +fn arrays() { + let data = [5u8; 99999]; + + let encoded = <[u8; 99999] as storey::containers::map::Key>::encode(&data); + let my_encoded = <[u8; 99999] as storey::containers::map::Key>::encode(&data); + + assert_eq!(encoded, my_encoded); + + let my_decoded = + <[u8; 99999] as storey::containers::map::OwnedKey>::from_bytes(&encoded).unwrap(); + assert_eq!(data, my_decoded); +}