1use serde::de::DeserializeOwned;
2use serde::Serialize;
3use std::marker::PhantomData;
4
5use cosmwasm_std::{
6 from_json, to_json_vec, Addr, CustomQuery, QuerierWrapper, StdError, StdResult, Storage,
7 WasmQuery,
8};
9
10use crate::{helpers::not_found_object_info, namespace::Namespace};
11
12pub struct Item<T> {
16 storage_key: Namespace,
18 data_type: PhantomData<T>,
20}
21
22impl<T> Item<T> {
23 pub const fn new(storage_key: &'static str) -> Self {
26 Item {
27 storage_key: Namespace::from_static_str(storage_key),
28 data_type: PhantomData,
29 }
30 }
31
32 pub fn new_dyn(storage_key: impl Into<Namespace>) -> Self {
35 Item {
36 storage_key: storage_key.into(),
37 data_type: PhantomData,
38 }
39 }
40}
41
42impl<T> Item<T>
43where
44 T: Serialize + DeserializeOwned,
45{
46 pub fn as_slice(&self) -> &[u8] {
48 self.storage_key.as_slice()
49 }
50
51 pub fn save(&self, store: &mut dyn Storage, data: &T) -> StdResult<()> {
53 store.set(self.storage_key.as_slice(), &to_json_vec(data)?);
54 Ok(())
55 }
56
57 pub fn remove(&self, store: &mut dyn Storage) {
58 store.remove(self.storage_key.as_slice());
59 }
60
61 pub fn load(&self, store: &dyn Storage) -> StdResult<T> {
63 if let Some(value) = store.get(self.storage_key.as_slice()) {
64 from_json(value)
65 } else {
66 let object_info = not_found_object_info::<T>(self.storage_key.as_slice());
67 Err(StdError::msg(format!("{object_info} not found")))
68 }
69 }
70
71 pub fn may_load(&self, store: &dyn Storage) -> StdResult<Option<T>> {
74 let value = store.get(self.storage_key.as_slice());
75 value.map(|v| from_json(v)).transpose()
76 }
77
78 pub fn exists(&self, store: &dyn Storage) -> bool {
80 store.get(self.storage_key.as_slice()).is_some()
81 }
82
83 pub fn update<A, E>(&self, store: &mut dyn Storage, action: A) -> Result<T, E>
89 where
90 A: FnOnce(T) -> Result<T, E>,
91 E: From<StdError>,
92 {
93 let input = self.load(store)?;
94 let output = action(input)?;
95 self.save(store, &output)?;
96 Ok(output)
97 }
98
99 pub fn query<Q: CustomQuery>(
104 &self,
105 querier: &QuerierWrapper<Q>,
106 remote_contract: Addr,
107 ) -> StdResult<T> {
108 let request = WasmQuery::Raw {
109 contract_addr: remote_contract.into(),
110 key: (self.storage_key.as_slice()).into(),
111 };
112 querier.query(&request.into())
113 }
114}
115
116#[cfg(test)]
117mod test {
118 use super::*;
119 use cosmwasm_std::testing::MockStorage;
120 use cosmwasm_std::{to_json_vec, StdError};
121 use serde::{Deserialize, Serialize};
122
123 #[derive(Serialize, Deserialize, PartialEq, Debug)]
124 struct Config {
125 pub owner: String,
126 pub max_tokens: i32,
127 }
128
129 const CONFIG: Item<Config> = Item::new("config");
131
132 #[test]
133 fn save_and_load() {
134 let mut store = MockStorage::new();
135
136 assert!(CONFIG.load(&store).is_err());
137 assert_eq!(CONFIG.may_load(&store).unwrap(), None);
138
139 let cfg = Config {
140 owner: "admin".to_string(),
141 max_tokens: 1234,
142 };
143 CONFIG.save(&mut store, &cfg).unwrap();
144
145 assert_eq!(cfg, CONFIG.load(&store).unwrap());
146 }
147
148 #[test]
149 fn owned_key_works() {
150 let mut store = MockStorage::new();
151
152 for i in 0..3 {
153 let key = format!("key{i}");
154 let item = Item::new_dyn(key);
155 item.save(&mut store, &i).unwrap();
156 }
157
158 assert_eq!(store.get(b"key0").unwrap(), b"0");
159 assert_eq!(store.get(b"key1").unwrap(), b"1");
160 assert_eq!(store.get(b"key2").unwrap(), b"2");
161 }
162
163 #[test]
164 fn exists_works() {
165 let mut store = MockStorage::new();
166
167 assert!(!CONFIG.exists(&store));
168
169 let cfg = Config {
170 owner: "admin".to_string(),
171 max_tokens: 1234,
172 };
173 CONFIG.save(&mut store, &cfg).unwrap();
174
175 assert!(CONFIG.exists(&store));
176
177 const OPTIONAL: Item<Option<u32>> = Item::new("optional");
178
179 assert!(!OPTIONAL.exists(&store));
180
181 OPTIONAL.save(&mut store, &None).unwrap();
182
183 assert!(OPTIONAL.exists(&store));
184 }
185
186 #[test]
187 fn remove_works() {
188 let mut store = MockStorage::new();
189
190 let cfg = Config {
192 owner: "admin".to_string(),
193 max_tokens: 1234,
194 };
195 CONFIG.save(&mut store, &cfg).unwrap();
196 assert_eq!(cfg, CONFIG.load(&store).unwrap());
197
198 CONFIG.remove(&mut store);
200 assert!(!CONFIG.exists(&store));
201
202 CONFIG.remove(&mut store);
204 assert!(!CONFIG.exists(&store));
205 }
206
207 #[test]
208 fn isolated_reads() {
209 let mut store = MockStorage::new();
210
211 let cfg = Config {
212 owner: "admin".to_string(),
213 max_tokens: 1234,
214 };
215 CONFIG.save(&mut store, &cfg).unwrap();
216
217 let reader = Item::<Config>::new("config");
218 assert_eq!(cfg, reader.load(&store).unwrap());
219
220 let other_reader = Item::<Config>::new("config2");
221 assert_eq!(other_reader.may_load(&store).unwrap(), None);
222 }
223
224 #[test]
225 fn update_success() {
226 let mut store = MockStorage::new();
227
228 let cfg = Config {
229 owner: "admin".to_string(),
230 max_tokens: 1234,
231 };
232 CONFIG.save(&mut store, &cfg).unwrap();
233
234 let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
235 c.max_tokens *= 2;
236 Ok(c)
237 });
238 let expected = Config {
239 owner: "admin".to_string(),
240 max_tokens: 2468,
241 };
242 assert_eq!(output.unwrap(), expected);
243 assert_eq!(CONFIG.load(&store).unwrap(), expected);
244 }
245
246 #[test]
247 fn update_can_change_variable_from_outer_scope() {
248 let mut store = MockStorage::new();
249 let cfg = Config {
250 owner: "admin".to_string(),
251 max_tokens: 1234,
252 };
253 CONFIG.save(&mut store, &cfg).unwrap();
254
255 let mut old_max_tokens = 0i32;
256 CONFIG
257 .update(&mut store, |mut c| -> StdResult<_> {
258 old_max_tokens = c.max_tokens;
259 c.max_tokens *= 2;
260 Ok(c)
261 })
262 .unwrap();
263 assert_eq!(old_max_tokens, 1234);
264 }
265
266 #[test]
267 fn update_does_not_change_data_on_error() {
268 let mut store = MockStorage::new();
269
270 let cfg = Config {
271 owner: "admin".to_string(),
272 max_tokens: 1234,
273 };
274 CONFIG.save(&mut store, &cfg).unwrap();
275
276 let output = CONFIG.update(&mut store, |_c| Err(StdError::msg("overflow")));
277 assert_eq!(
278 "kind: Other, error: overflow",
279 output.unwrap_err().to_string()
280 );
281 assert_eq!(CONFIG.load(&store).unwrap(), cfg);
282 }
283
284 #[test]
285 fn update_supports_custom_errors() {
286 #[derive(Debug)]
287 enum MyError {
288 Std(StdError),
289 Foo,
290 }
291
292 impl From<StdError> for MyError {
293 fn from(original: StdError) -> MyError {
294 MyError::Std(original)
295 }
296 }
297
298 let mut store = MockStorage::new();
299
300 let cfg = Config {
301 owner: "admin".to_string(),
302 max_tokens: 1234,
303 };
304 CONFIG.save(&mut store, &cfg).unwrap();
305
306 let res = CONFIG.update(&mut store, |mut c| {
307 if c.max_tokens > 5000 {
308 return Err(MyError::Foo);
309 }
310 if c.max_tokens > 20 {
311 return Err(StdError::msg("broken stuff").into()); }
313 if c.max_tokens > 10 {
314 to_json_vec(&c)?; }
316 c.max_tokens += 20;
317 Ok(c)
318 });
319
320 match res.unwrap_err() {
321 MyError::Std(std) if std.to_string() == "kind: Other, error: broken stuff" => {}
322 err => panic!("Unexpected error: {err:?}"),
323 }
324 assert_eq!(CONFIG.load(&store).unwrap(), cfg);
325 }
326
327 #[test]
328 fn readme_works() -> StdResult<()> {
329 let mut store = MockStorage::new();
330
331 let empty = CONFIG.may_load(&store)?;
334 assert_eq!(None, empty);
335 let cfg = Config {
336 owner: "admin".to_string(),
337 max_tokens: 1234,
338 };
339 CONFIG.save(&mut store, &cfg)?;
340 let loaded = CONFIG.load(&store)?;
341 assert_eq!(cfg, loaded);
342
343 let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
346 c.max_tokens *= 2;
347 Ok(c)
348 })?;
349 assert_eq!(2468, output.max_tokens);
350
351 let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
353 Err(StdError::msg("failure mode"))
354 });
355 assert!(failed.is_err());
356
357 let loaded = CONFIG.load(&store)?;
359 let expected = Config {
360 owner: "admin".to_string(),
361 max_tokens: 2468,
362 };
363 assert_eq!(expected, loaded);
364
365 CONFIG.remove(&mut store);
367 let empty = CONFIG.may_load(&store)?;
368 assert_eq!(None, empty);
369
370 Ok(())
371 }
372}