orbis_plugin_api/sdk/
state.rs

1//! Type-safe state management for plugins.
2//!
3//! Provides an ergonomic API for storing and retrieving plugin state.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use orbis_plugin_api::sdk::state;
9//!
10//! // Set a value
11//! state::set("counter", &42)?;
12//!
13//! // Get a value
14//! let counter: i32 = state::get("counter")?.unwrap_or(0);
15//!
16//! // Remove a value
17//! state::remove("counter")?;
18//! ```
19
20#[allow(unused_imports)]
21use super::error::{Error, Result};
22use serde::{de::DeserializeOwned, Serialize};
23
24/// Get a value from plugin state.
25///
26/// Returns `None` if the key doesn't exist, or `Some(value)` if it does.
27///
28/// # Errors
29///
30/// Returns an error if deserialization fails.
31#[cfg(target_arch = "wasm32")]
32pub fn get<T: DeserializeOwned>(key: &str) -> Result<Option<T>> {
33    let ptr = unsafe {
34        super::ffi::state_get(key.as_ptr() as i32, key.len() as i32)
35    };
36
37    if ptr == 0 {
38        return Ok(None);
39    }
40
41    let bytes = unsafe { super::ffi::read_length_prefixed(ptr) };
42    let value: T = serde_json::from_slice(&bytes)?;
43    Ok(Some(value))
44}
45
46/// Get a value from plugin state (non-WASM stub)
47#[cfg(not(target_arch = "wasm32"))]
48pub fn get<T: DeserializeOwned>(_key: &str) -> Result<Option<T>> {
49    Ok(None)
50}
51
52/// Get a value or return a default.
53///
54/// # Example
55///
56/// ```rust,ignore
57/// let count: i32 = state::get_or("counter", 0)?;
58/// ```
59#[inline]
60pub fn get_or<T: DeserializeOwned>(key: &str, default: T) -> Result<T> {
61    get(key).map(|opt| opt.unwrap_or(default))
62}
63
64/// Get a value or compute a default.
65#[inline]
66pub fn get_or_else<T: DeserializeOwned, F: FnOnce() -> T>(key: &str, f: F) -> Result<T> {
67    get(key).map(|opt| opt.unwrap_or_else(f))
68}
69
70/// Set a value in plugin state.
71///
72/// # Errors
73///
74/// Returns an error if serialization fails or the host rejects the operation.
75#[cfg(target_arch = "wasm32")]
76pub fn set<T: Serialize>(key: &str, value: &T) -> Result<()> {
77    let value_json = serde_json::to_vec(value)?;
78
79    let result = unsafe {
80        super::ffi::state_set(
81            key.as_ptr() as i32,
82            key.len() as i32,
83            value_json.as_ptr() as i32,
84            value_json.len() as i32,
85        )
86    };
87
88    if result == 1 {
89        Ok(())
90    } else {
91        Err(Error::state(format!("Failed to set state key: {}", key)))
92    }
93}
94
95/// Set a value in plugin state (non-WASM stub)
96#[cfg(not(target_arch = "wasm32"))]
97pub fn set<T: Serialize>(_key: &str, _value: &T) -> Result<()> {
98    Ok(())
99}
100
101/// Remove a value from plugin state.
102///
103/// # Errors
104///
105/// Returns an error if the host rejects the operation.
106#[cfg(target_arch = "wasm32")]
107pub fn remove(key: &str) -> Result<()> {
108    let result = unsafe {
109        super::ffi::state_remove(key.as_ptr() as i32, key.len() as i32)
110    };
111
112    if result == 1 {
113        Ok(())
114    } else {
115        Err(Error::state(format!("Failed to remove state key: {}", key)))
116    }
117}
118
119/// Remove a value from plugin state (non-WASM stub)
120#[cfg(not(target_arch = "wasm32"))]
121pub fn remove(_key: &str) -> Result<()> {
122    Ok(())
123}
124
125/// Update a value in state using a function.
126///
127/// If the key doesn't exist, uses the default value.
128///
129/// # Example
130///
131/// ```rust,ignore
132/// state::update("counter", 0, |n| n + 1)?;
133/// ```
134pub fn update<T, F>(key: &str, default: T, f: F) -> Result<T>
135where
136    T: DeserializeOwned + Serialize + Clone,
137    F: FnOnce(T) -> T,
138{
139    let current = get(key)?.unwrap_or(default);
140    let new_value = f(current);
141    set(key, &new_value)?;
142    Ok(new_value)
143}
144
145/// Increment a numeric value in state.
146///
147/// # Example
148///
149/// ```rust,ignore
150/// let new_count = state::increment("counter")?;
151/// ```
152pub fn increment(key: &str) -> Result<i64> {
153    update(key, 0i64, |n| n + 1)
154}
155
156/// Decrement a numeric value in state.
157pub fn decrement(key: &str) -> Result<i64> {
158    update(key, 0i64, |n| n - 1)
159}
160
161/// Append to a list in state.
162///
163/// # Example
164///
165/// ```rust,ignore
166/// state::push("items", &"new item")?;
167/// ```
168pub fn push<T>(key: &str, value: &T) -> Result<()>
169where
170    T: Serialize + DeserializeOwned,
171{
172    let mut items: Vec<T> = get(key)?.unwrap_or_default();
173    // We need to serialize and deserialize to get the value in the right format
174    let value_json = serde_json::to_value(value)?;
175    let typed_value: T = serde_json::from_value(value_json)?;
176    items.push(typed_value);
177    set(key, &items)
178}
179
180/// Get the length of a list in state.
181pub fn len(key: &str) -> Result<usize> {
182    let items: Vec<serde_json::Value> = get(key)?.unwrap_or_default();
183    Ok(items.len())
184}
185
186/// Check if a key exists in state.
187#[cfg(target_arch = "wasm32")]
188pub fn exists(key: &str) -> bool {
189    let ptr = unsafe {
190        super::ffi::state_get(key.as_ptr() as i32, key.len() as i32)
191    };
192    ptr != 0
193}
194
195/// Check if a key exists in state (non-WASM stub)
196#[cfg(not(target_arch = "wasm32"))]
197pub fn exists(_key: &str) -> bool {
198    false
199}
200
201/// Scoped state access with a prefix.
202///
203/// Useful for organizing state by feature or entity.
204///
205/// # Example
206///
207/// ```rust,ignore
208/// let user_state = state::scoped("user:123");
209/// user_state.set("name", &"John")?;
210/// let name: String = user_state.get("name")?.unwrap();
211/// ```
212pub struct ScopedState {
213    prefix: String,
214}
215
216impl ScopedState {
217    /// Create a new scoped state accessor
218    #[must_use]
219    pub fn new(prefix: impl Into<String>) -> Self {
220        let mut prefix = prefix.into();
221        if !prefix.ends_with(':') {
222            prefix.push(':');
223        }
224        Self { prefix }
225    }
226
227    fn key(&self, name: &str) -> String {
228        format!("{}{}", self.prefix, name)
229    }
230
231    /// Get a value from scoped state
232    pub fn get<T: DeserializeOwned>(&self, name: &str) -> Result<Option<T>> {
233        get(&self.key(name))
234    }
235
236    /// Set a value in scoped state
237    pub fn set<T: Serialize>(&self, name: &str, value: &T) -> Result<()> {
238        set(&self.key(name), value)
239    }
240
241    /// Remove a value from scoped state
242    pub fn remove(&self, name: &str) -> Result<()> {
243        remove(&self.key(name))
244    }
245
246    /// Check if a key exists in scoped state
247    pub fn exists(&self, name: &str) -> bool {
248        exists(&self.key(name))
249    }
250}
251
252/// Create a scoped state accessor
253#[inline]
254#[must_use]
255pub fn scoped(prefix: impl Into<String>) -> ScopedState {
256    ScopedState::new(prefix)
257}