neo_devpack/storage.rs
1//! Storage utilities for Neo N3 smart contracts.
2//!
3//! This module provides convenient functions for storing and retrieving
4//! structured data using JSON serialization.
5
6use crate::{NeoStorage, NeoStorageContext};
7use neo_types::{NeoByteString, NeoIterator, NeoResult, NeoStruct, NeoValue};
8use serde::{Deserialize, Serialize};
9use serde_json::Value as JsonValue;
10
11/// Reads a JSON-serialized value from a NeoByteString.
12///
13/// # Type Parameters
14/// * `T` - The type to deserialize
15///
16/// # Arguments
17/// * `bytes` - The bytes containing JSON data
18///
19/// # Returns
20/// * `Some(T)` if deserialization succeeds
21/// * `None` if deserialization fails
22pub fn read_json<T: for<'de> Deserialize<'de>>(bytes: &NeoByteString) -> Option<T> {
23 serde_json::from_slice(bytes.as_slice()).ok()
24}
25
26/// Writes a value as JSON to a NeoByteString.
27///
28/// # Type Parameters
29/// * `T` - The type to serialize
30///
31/// # Arguments
32/// * `value` - The value to serialize
33///
34/// # Returns
35/// A NeoByteString containing the JSON representation, or an empty byte string on error.
36pub fn write_json<T: Serialize>(value: &T) -> NeoByteString {
37 match serde_json::to_vec(value) {
38 Ok(data) => NeoByteString::from_slice(&data),
39 Err(_) => NeoByteString::new(Vec::new()),
40 }
41}
42
43/// Loads a typed value from storage.
44///
45/// # Type Parameters
46/// * `T` - The type to load
47///
48/// # Arguments
49/// * `ctx` - The storage context
50/// * `key` - The storage key
51///
52/// # Returns
53/// * `Some(T)` if the key exists and deserialization succeeds
54/// * `None` if the key doesn't exist or deserialization fails
55pub fn load<T: for<'de> Deserialize<'de>>(ctx: &NeoStorageContext, key: &[u8]) -> Option<T> {
56 let key_bytes = NeoByteString::from_slice(key);
57 let bytes = NeoStorage::get(ctx, &key_bytes).ok()?;
58 if bytes.is_empty() {
59 None
60 } else {
61 read_json(&bytes)
62 }
63}
64
65/// Stores a typed value to storage.
66///
67/// # Type Parameters
68/// * `T` - The type to store
69///
70/// # Arguments
71/// * `ctx` - The storage context
72/// * `key` - The storage key
73/// * `value` - The value to store
74///
75/// # Returns
76/// * `Ok(())` on success
77/// * `Err(NeoError)` if storage fails (e.g., read-only context)
78pub fn store<T: Serialize>(ctx: &NeoStorageContext, key: &[u8], value: &T) -> NeoResult<()> {
79 let key_bytes = NeoByteString::from_slice(key);
80 NeoStorage::put(ctx, &key_bytes, &write_json(value))
81}
82
83/// Deletes a key from storage.
84///
85/// # Arguments
86/// * `ctx` - The storage context
87/// * `key` - The storage key to delete
88///
89/// # Returns
90/// * `Ok(())` on success
91/// * `Err(NeoError)` if deletion fails (e.g., read-only context)
92pub fn delete(ctx: &NeoStorageContext, key: &[u8]) -> NeoResult<()> {
93 let key_bytes = NeoByteString::from_slice(key);
94 NeoStorage::delete(ctx, &key_bytes)
95}
96
97/// Finds all storage entries with the given prefix.
98///
99/// # Arguments
100/// * `ctx` - The storage context
101/// * `prefix` - The key prefix to search for
102///
103/// # Returns
104/// * `Ok(NeoIterator<NeoValue>)` containing matching entries
105/// * `Err(NeoError)` if the search fails
106pub fn find_prefix(ctx: &NeoStorageContext, prefix: &[u8]) -> NeoResult<NeoIterator<NeoValue>> {
107 let prefix_bytes = NeoByteString::from_slice(prefix);
108 NeoStorage::find(ctx, &prefix_bytes)
109}
110
111/// Creates a NeoStruct entry with key and value fields.
112///
113/// This is useful for returning storage entries from contract methods.
114pub fn struct_entry(key: NeoByteString, value: NeoByteString) -> NeoValue {
115 let mut s = NeoStruct::new();
116 s.set_field("key", NeoValue::from(key));
117 s.set_field("value", NeoValue::from(value));
118 NeoValue::from(s)
119}
120
121/// Converts a NeoValue containing a ByteString to a JSON value.
122///
123/// # Arguments
124/// * `value` - The NeoValue to convert
125///
126/// # Returns
127/// * `Some(JsonValue)` if the value is a valid ByteString containing JSON
128/// * `None` otherwise
129pub fn value_to_json(value: &NeoValue) -> Option<JsonValue> {
130 value
131 .as_byte_string()
132 .and_then(|bytes| serde_json::from_slice(bytes.as_slice()).ok())
133}