Skip to main content

cinderblock_core/data_layer/
in_memory.rs

1use std::{
2    any::{Any, TypeId},
3    collections::HashMap,
4    sync::{Arc, LazyLock},
5};
6
7use tokio::sync::RwLock;
8
9use crate::{
10    DestroyError, ListError, PerformRead, PerformReadOne, ReadAction, ReadError, Resource,
11    UpdateError, data_layer::DataLayer,
12};
13
14type State =
15    LazyLock<Arc<RwLock<HashMap<TypeId, HashMap<String, Box<dyn Any + Send + Sync + 'static>>>>>>;
16
17static STATE: State = LazyLock::new(Arc::default);
18
19#[derive(Debug)]
20pub struct InMemoryDataLayer {}
21impl InMemoryDataLayer {
22    pub(crate) fn new() -> Self {
23        Self {}
24    }
25
26    /// Load all rows of a given resource type from the global in-memory
27    /// store.
28    ///
29    /// This is used by generated relation-loading code to batch-fetch
30    /// related resources. Returns an empty `Vec` if no rows of the
31    /// requested type exist.
32    pub async fn load_all<R: Resource + 'static>(&self) -> Vec<R> {
33        let state = STATE.clone();
34        let state = state.read().await;
35
36        state
37            .get(&TypeId::of::<R>())
38            .into_iter()
39            .flat_map(|map| map.values())
40            .filter_map(|boxed| boxed.downcast_ref::<R>())
41            .cloned()
42            .collect()
43    }
44}
45
46impl<R: Resource + 'static> DataLayer<R> for InMemoryDataLayer {
47    async fn create(&self, resource: R) -> Result<R, crate::CreateError> {
48        let state = STATE.clone();
49        let mut state = state.write().await;
50
51        let map = state.entry(TypeId::of::<R>()).or_default();
52        map.insert(
53            resource.primary_key().to_string(),
54            Box::new(resource.clone()),
55        );
56
57        Ok(resource)
58    }
59
60    async fn read(&self, primary_key: &R::PrimaryKey) -> Result<R, crate::ReadError> {
61        let state = STATE.clone();
62        let state = state.read().await;
63
64        let key = primary_key.to_string();
65
66        state
67            .get(&TypeId::of::<R>())
68            .and_then(|map| map.get(&key))
69            .and_then(|boxed| boxed.downcast_ref::<R>())
70            .cloned()
71            .ok_or_else(|| ReadError::NotFound { primary_key: key })
72    }
73
74    async fn update(&self, resource: R) -> Result<(), crate::UpdateError> {
75        let state = STATE.clone();
76        let mut state = state.write().await;
77
78        let key = resource.primary_key().to_string();
79
80        let map = state
81            .get_mut(&TypeId::of::<R>())
82            .ok_or_else(|| UpdateError::NotFound {
83                primary_key: key.clone(),
84            })?;
85
86        if !map.contains_key(&key) {
87            return Err(UpdateError::NotFound { primary_key: key });
88        }
89
90        map.insert(key, Box::new(resource.clone()));
91
92        Ok(())
93    }
94
95    async fn destroy(&self, primary_key: &R::PrimaryKey) -> Result<R, crate::DestroyError> {
96        let state = STATE.clone();
97        let mut state = state.write().await;
98
99        let key = primary_key.to_string();
100
101        let map = state
102            .get_mut(&TypeId::of::<R>())
103            .ok_or_else(|| DestroyError::NotFound {
104                primary_key: key.clone(),
105            })?;
106
107        let boxed = map.remove(&key).ok_or_else(|| DestroyError::NotFound {
108            primary_key: key.clone(),
109        })?;
110
111        boxed
112            .downcast::<R>()
113            .map(|r| *r)
114            .map_err(|_| DestroyError::DataLayer("failed to downcast destroyed resource".into()))
115    }
116}
117
118/// Filter trait for non-paged in-memory read actions. The generated
119/// `resource!` macro emits an impl for each non-paged read action.
120pub trait InMemoryReadAction: ReadAction {
121    fn filter(row: &Self::Output, args: &Self::Arguments) -> bool;
122}
123
124/// Filter trait for paged in-memory read actions. Same filter interface as
125/// [`InMemoryReadAction`], but applied to paged actions which additionally
126/// require `Arguments: Paged`.
127pub trait InMemoryPagedReadAction: ReadAction {
128    fn filter(row: &Self::Output, args: &Self::Arguments) -> bool;
129}
130
131/// Unified execution trait that bridges the filter-based traits
132/// ([`InMemoryReadAction`] and [`InMemoryPagedReadAction`]) to the
133/// framework's [`PerformRead`] trait.
134///
135/// The `resource!` macro generates explicit impls of this trait for each
136/// read action, delegating to the appropriate filter logic. A single
137/// blanket `PerformRead` impl then dispatches to this trait.
138pub trait InMemoryPerformRead: ReadAction {
139    fn execute(all: impl Iterator<Item = Self::Output>, args: &Self::Arguments) -> Self::Response;
140}
141
142/// Single `PerformRead` impl for `InMemoryDataLayer` that delegates to
143/// `InMemoryPerformRead::execute`.
144impl<R, A> PerformRead<A> for InMemoryDataLayer
145where
146    R: Resource + 'static,
147    A: ReadAction<Output = R> + InMemoryPerformRead + 'static,
148{
149    async fn read(&self, args: &A::Arguments) -> Result<A::Response, ListError> {
150        let state = STATE.clone();
151        let state = state.read().await;
152
153        let type_map = state.get(&TypeId::of::<R>());
154        let all = type_map
155            .iter()
156            .flat_map(|map| map.values())
157            .filter_map(|boxed| boxed.downcast_ref::<R>())
158            .cloned();
159
160        Ok(A::execute(all, args))
161    }
162}
163
164/// Single `PerformReadOne` impl for `InMemoryDataLayer` that delegates
165/// to the underlying `DataLayer::read` by primary key.
166///
167/// Get-actions set `Arguments = PrimaryKey` and `Response = Resource`,
168/// so this blanket impl covers all get-actions without per-action codegen.
169impl<R, A> PerformReadOne<A> for InMemoryDataLayer
170where
171    R: Resource + 'static,
172    A: ReadAction<Output = R, Arguments = R::PrimaryKey, Response = R> + 'static,
173{
174    async fn read_one(&self, args: &A::Arguments) -> Result<A::Response, ReadError> {
175        <Self as DataLayer<R>>::read(self, args).await
176    }
177}