From 68979d00330d9e6ba053e7b40e5dfce1dead8956 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 9 Dec 2024 16:32:56 +0100 Subject: [PATCH] storey: document Map --- docs-test-gen/Cargo.lock | 1 + docs-test-gen/Cargo.toml | 1 + src/pages/storey/containers/map.mdx | 234 ++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) diff --git a/docs-test-gen/Cargo.lock b/docs-test-gen/Cargo.lock index f4cd255b..c177d330 100644 --- a/docs-test-gen/Cargo.lock +++ b/docs-test-gen/Cargo.lock @@ -852,6 +852,7 @@ dependencies = [ "schemars", "serde", "sha2 0.10.8", + "storey", "strum", "sylvia", "thiserror 1.0.69", diff --git a/docs-test-gen/Cargo.toml b/docs-test-gen/Cargo.toml index d6a7916c..abf8b064 100644 --- a/docs-test-gen/Cargo.toml +++ b/docs-test-gen/Cargo.toml @@ -29,3 +29,4 @@ serde = "*" cw-storey = "*" schemars = "0.8.21" # Used in entrypoint example thiserror = "1.0.65" +storey = "*" diff --git a/src/pages/storey/containers/map.mdx b/src/pages/storey/containers/map.mdx index 6ac9a7ef..742f3021 100644 --- a/src/pages/storey/containers/map.mdx +++ b/src/pages/storey/containers/map.mdx @@ -5,3 +5,237 @@ tags: ["storey", "containers"] import { Callout } from "nextra/components"; # Map + +A `Map` is a container that maps keys to instances of another container. + +Most commonly, you will use a `Map` to store a list of items. For example, the signature could be a +`Map>` to store a list of signatures for each user. (See the first example.) + +## Examples + +### Keeping balances + +Let's say you want to keep track of the balances of each user. You can do this with a +`Map>`. + +```rust template="storage" showLineNumbers {6,8,10,12,14} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; + +const BALANCES_IX: u8 = 0; + +let balances: Map> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +assert_eq!(access.entry("alice").get().unwrap(), None); + +access.entry_mut("alice").set(&Uint128::new(1000)).unwrap(); + +assert_eq!(access.entry("alice").get().unwrap(), Some(Uint128::new(1000))); +``` + +- _line 6:_ Here we construct the `Map` facade. The constructor takes a key, which is the prefix of + the keys in the underlying storage backend. +- _line 8:_ The [`access`] method returns a [`MapAccess`] entity, which allows manipulating the map. +- _line 10:_ Here we try to access the balance of `alice`. Since she doesn't have one yet, it + returns `None`. The [`entry`] method returns an [`ItemAccess`] entity, which allows manipulating + the item stored under the key `alice`. +- _line 12:_ Here we set Alice's balance to `1000`. +- _line 14:_ We check that the balance is now `1000`. + +#### Iterating over the balances + +Iterating over the balances is pretty straightforward. The [`keys`] method returns an iterator over +the keys, the [`values`] method returns an iterator over the values, and the [`pairs`] method +returns an iterator over both. + +```rust template="storage" showLineNumbers {4, 17, 19} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; + +use storey::containers::IterableAccessor as _; + +const BALANCES_IX: u8 = 1; + +let balances: Map> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("bob").set(&Uint128::new(500)).unwrap(); +access.entry_mut("carol").set(&Uint128::new(1500)).unwrap(); +access.entry_mut("dave").set(&Uint128::new(2000)).unwrap(); + +assert_eq!( + access.pairs().collect::, _>>().unwrap(), + vec![ + (("bob".to_string(), ()), Uint128::new(500)), + (("carol".to_string(), ()), Uint128::new(1500)), + (("dave".to_string(), ()), Uint128::new(2000)), + ] +); + +assert_eq!( + access.keys().collect::, _>>().unwrap(), + vec![("bob".to_string(), ()), ("carol".to_string(), ()), ("dave".to_string(), ())] +); + +assert_eq!( + access.values().collect::, _>>().unwrap(), + vec![Uint128::new(500), Uint128::new(1500), Uint128::new(2000)] +); +``` + +- _line 4:_ Here we import the [`IterableAccessor`] trait. This trait provides unbounded iteration. +- _line 17:_ The `pairs` method returns an iterator over the key-value pairs. +- _line 19:_ Notice the key type is `(String, ())`. This is likely to become just `String` in the + future. For now, consider this a quirk of the design. This will make more sense once you get to + composite maps. + +#### Bounded iteration + +Bounded iteration is also supported in many cases. + +```rust template="storage" showLineNumbers {17} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; + +use storey::containers::BoundedIterableAccessor as _; + +const BALANCES_IX: u8 = 1; + +let balances: Map> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("bob").set(&Uint128::new(500)).unwrap(); +access.entry_mut("carol").set(&Uint128::new(1500)).unwrap(); +access.entry_mut("dave").set(&Uint128::new(2000)).unwrap(); + +assert_eq!( + access.bounded_pairs(Some("bob"), Some("dave")).collect::, _>>().unwrap(), + vec![(("bob".to_string(), ()), Uint128::new(500)), (("carol".to_string(), ()), Uint128::new(1500))] +); +``` + +Here we used the [`bounded_pairs`] method to iterate over some key-value pairs. Other bounded +methods are also available: [`bounded_keys`] and [`bounded_values`]. + +The bounds are provided as arguments to the methods. Currently, the bounds are inclusive on the +lower bound and exclusive on the upper bound. In a future release (soon!) this will be configurable. + +### Keeping balances with composition + +Alright, let's say this time you'd also like to keep track of the balances of each user, but each +can have multiple different tokens. This is where composition comes in. + +```rust template="storage" showLineNumbers {6, 10-14} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; + +const BALANCES_IX: u8 = 0; + +let balances: Map>> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("alice").entry_mut("USDT").set(&Uint128::new(1000)).unwrap(); +access.entry_mut("alice").entry_mut("OSMO").set(&Uint128::new(2000)).unwrap(); + +assert_eq!(access.entry("alice").entry("USDT").get().unwrap(), Some(Uint128::new(1000))); +assert_eq!(access.entry("alice").entry("OSMO").get().unwrap(), Some(Uint128::new(2000))); +``` + +This example is similar to the previous one, but this time we have an extra level of nesting. + +First of all, our type is `Map>>`. The outer map maps user +addresses to inner maps. The inner map maps token denominations to actual balances. + +When we access the stuff, the first [`entry`]/[`entry_mut`] call accesses a record in the outer map, +and the second one accesses a record in the inner map. + + + In [`cw-storage-plus`], you can achieve the same + effect using composite keys (tuples). The example above would then use something like + `cw_storage_plus::Map<(String, String), Uint128>`. + + +#### Iterating over the balances + +Let's take a look at what iteration looks like with a composite map. + +```rust template="storage" showLineNumbers {18, 27} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; +use cosmwasm_std::Order; + +use storey::containers::IterableAccessor as _; + +const BALANCES_IX: u8 = 1; + +let balances: Map>> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("alice").entry_mut("USDT").set(&1000).unwrap(); +access.entry_mut("alice").entry_mut("OSMO").set(&2000).unwrap(); +access.entry_mut("bob").entry_mut("USDT").set(&1500).unwrap(); + +assert_eq!( + access.pairs().collect::, _>>().unwrap(), + vec![ + (("bob".into(), ("USDT".into(), ())), 1500), + (("alice".into(), ("OSMO".into(), ())), 2000), + (("alice".into(), ("USDT".into(), ())), 1000), + ] +); + +assert_eq!( + access.entry("alice").pairs().collect::, _>>().unwrap(), + vec![(("OSMO".into(), ()), 2000), (("USDT".into(), ()), 1000)] +); +``` + +Here we iterated twice, but each time we got a different view of the data. Each iteration was at a +different level. + +- _line 18:_ We call `pairs` on the outer map, which gives us all the entries. +- _line 27:_ We call `pairs` on the inner map under the key `alice`, which gives us all of Alice's + balances. + +We can of course do the same with the `keys` and `values` methods. + + + When iterating over the entries in a `Map` using the `pairs`, `keys`, and `values` methods, the + **order of the keys is not guaranteed to be sensible** (though it is deterministic). If you need a + sensible order, try using the bounded iterators. If they do not exist (`BoundedIterableAccessor` is not implemented for the accessor), sensibly ordered iteration is not + possible. + +Bounded or sensibly ordered iteration is not possible when both of the following conditions are met: + +- The key is dynamically sized (e.g. `String`, `Vec`, etc.). +- The value type is a collection (`Map`, `Column`, etc.) rather than something like `Item`. + +This is why, in the example above, bounded iteration (and a sensible order) is only possible for the +inner map. + + + +[`cw-storage-plus`]: /cw-storage-plus/containers/map +[`bounded_pairs`]: + https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_pairs +[`bounded_keys`]: + https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_keys +[`bounded_values`]: + https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_values +[`access`]: https://docs.rs/storey/latest/storey/containers/map/struct.Map.html#method.access +[`entry`]: https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html#method.entry +[`entry_mut`]: + https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html#method.entry_mut +[`keys`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.keys +[`values`]: + https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.values +[`pairs`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.pairs +[`ItemAccess`]: https://docs.rs/storey/latest/storey/containers/struct.ItemAccess.html +[`MapAccess`]: https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html +[`IterableAccessor`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html