Skip to main content

ash_flare/
types.rs

1//! Common types used throughout the supervision tree
2
3use crate::restart::RestartPolicy;
4use serde::{Deserialize, Serialize};
5
6use dashmap::DashMap;
7use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
8use std::sync::Arc;
9
10/// Result of a worker's execution
11#[derive(
12    Debug,
13    Clone,
14    Copy,
15    PartialEq,
16    Eq,
17    Serialize,
18    Deserialize,
19    Archive,
20    RkyvSerialize,
21    RkyvDeserialize,
22)]
23#[rkyv(derive(Debug))]
24pub enum ChildExitReason {
25    /// Normal termination
26    Normal,
27    /// Abnormal termination (error or panic)
28    Abnormal,
29    /// Shutdown requested
30    Shutdown,
31}
32
33/// Child identifier type
34pub type ChildId = String;
35
36/// Information about a child process
37#[derive(Debug, Clone)]
38pub struct ChildInfo {
39    /// Unique identifier for the child
40    pub id: ChildId,
41    /// Type of child (Worker or Supervisor)
42    pub child_type: ChildType,
43    /// Restart policy for the child (None for supervisors)
44    pub restart_policy: Option<RestartPolicy>,
45}
46
47/// Type of child in supervision tree
48#[derive(
49    Debug,
50    Clone,
51    Copy,
52    PartialEq,
53    Eq,
54    Serialize,
55    Deserialize,
56    Archive,
57    RkyvSerialize,
58    RkyvDeserialize,
59)]
60#[rkyv(derive(Debug))]
61pub enum ChildType {
62    /// A worker process
63    Worker,
64    /// A nested supervisor
65    Supervisor,
66}
67
68/// Shared context for stateful workers with in-memory key-value store.
69///
70/// Provides a process-local, concurrency-safe storage for workers to share state.
71/// The store is backed by `DashMap` for lock-free concurrent access.
72///
73/// # Performance
74/// - Uses `DashMap` for lock-free concurrent operations
75/// - Cloning is cheap (only clones the `Arc`, not the data)
76#[derive(Clone, Debug)]
77pub struct WorkerContext {
78    store: Arc<DashMap<String, serde_json::Value>>,
79}
80
81impl WorkerContext {
82    /// Creates a new empty `WorkerContext`.
83    #[must_use]
84    pub fn new() -> Self {
85        Self {
86            store: Arc::new(DashMap::new()),
87        }
88    }
89
90    /// Gets a value from the store by key.
91    ///
92    /// Returns `None` if the key doesn't exist.
93    ///
94    /// Note: This method clones the value. For read-heavy workloads,
95    /// consider caching values or using primitives that are cheap to clone.
96    #[inline]
97    #[must_use]
98    pub fn get(&self, key: &str) -> Option<serde_json::Value> {
99        self.store.get(key).map(|entry| entry.value().clone())
100    }
101
102    /// Gets a value and applies a function to it without cloning.
103    ///
104    /// This is more efficient than `get()` when you only need to inspect the value.
105    ///
106    /// # Examples
107    /// ```
108    /// # use ash_flare::WorkerContext;
109    /// let ctx = WorkerContext::new();
110    /// ctx.set("count", serde_json::json!(42));
111    /// let is_positive = ctx.with_value("count", |v| {
112    ///     v.and_then(|val| val.as_i64()).map(|n| n > 0).unwrap_or(false)
113    /// });
114    /// assert!(is_positive);
115    /// ```
116    #[inline]
117    pub fn with_value<F, R>(&self, key: &str, f: F) -> R
118    where
119        F: FnOnce(Option<&serde_json::Value>) -> R,
120    {
121        match self.store.get(key) {
122            Some(entry) => f(Some(entry.value())),
123            None => f(None),
124        }
125    }
126
127    /// Sets a value in the store.
128    #[inline]
129    pub fn set(&self, key: impl Into<String>, value: serde_json::Value) {
130        self.store.insert(key.into(), value);
131    }
132
133    /// Deletes a key from the store.
134    ///
135    /// Returns the previous value if it existed.
136    #[inline]
137    #[must_use]
138    pub fn delete(&self, key: &str) -> Option<serde_json::Value> {
139        self.store.remove(key).map(|(_, v)| v)
140    }
141
142    /// Returns the number of key-value pairs in the store.
143    #[inline]
144    #[must_use]
145    pub fn len(&self) -> usize {
146        self.store.len()
147    }
148
149    /// Returns `true` if the store contains no key-value pairs.
150    #[inline]
151    #[must_use]
152    pub fn is_empty(&self) -> bool {
153        self.store.is_empty()
154    }
155
156    /// Checks if a key exists in the store without retrieving the value.
157    #[inline]
158    #[must_use]
159    pub fn contains_key(&self, key: &str) -> bool {
160        self.store.contains_key(key)
161    }
162
163    /// Updates a value in the store using a function.
164    ///
165    /// The function receives the current value (or `None` if the key doesn't exist)
166    /// and returns an `Option` to control the update:
167    /// - `Some(value)` - Insert or update the key with the new value
168    /// - `None` - **Remove the key from the store** (if it existed)
169    ///
170    /// # Warning
171    /// Returning `None` from the update function will **delete the key** from the store.
172    /// This is useful for conditional removal but can lead to unexpected data loss if not intended.
173    ///
174    /// # Examples
175    /// ```
176    /// # use ash_flare::WorkerContext;
177    /// let ctx = WorkerContext::new();
178    ///
179    /// // Increment a counter, initializing to 1 if it doesn't exist
180    /// ctx.update("counter", |v| {
181    ///     let count = v.and_then(|v| v.as_u64()).unwrap_or(0);
182    ///     Some(serde_json::json!(count + 1))
183    /// });
184    /// assert_eq!(ctx.get("counter").and_then(|v| v.as_u64()), Some(1));
185    ///
186    /// // Conditionally remove a key by returning None
187    /// ctx.set("temp", serde_json::json!("remove me"));
188    /// ctx.update("temp", |_| None); // Key is now deleted
189    /// assert!(!ctx.contains_key("temp"));
190    /// ```
191    pub fn update<F>(&self, key: &str, f: F)
192    where
193        F: FnOnce(Option<serde_json::Value>) -> Option<serde_json::Value>,
194    {
195        match self.store.entry(key.to_owned()) {
196            dashmap::mapref::entry::Entry::Occupied(mut entry) => {
197                let old_value = entry.get().clone();
198                match f(Some(old_value)) {
199                    Some(new_value) => {
200                        entry.insert(new_value);
201                    }
202                    None => {
203                        entry.remove();
204                    }
205                }
206            }
207            dashmap::mapref::entry::Entry::Vacant(entry) => {
208                if let Some(new_value) = f(None) {
209                    entry.insert(new_value);
210                }
211            }
212        }
213    }
214}
215
216impl Default for WorkerContext {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222// ============================================================================
223// Beam-ready Foundation Types
224// ============================================================================
225
226/// Process identifier (Beam-style) - foundation for future linking/monitoring
227#[allow(dead_code)]
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
229pub struct Pid(pub u64);
230
231/// Extended exit reason for future Beam-like semantics
232#[allow(dead_code)]
233#[derive(Debug, Clone)]
234pub enum ExitReason {
235    /// Normal termination
236    Normal,
237    /// Killed/aborted
238    Killed,
239    /// Terminated with error
240    Error(String),
241}