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::{PerformRead, ReadAction, Resource, data_layer::DataLayer};
10
11type State =
12    LazyLock<Arc<RwLock<HashMap<TypeId, HashMap<String, Box<dyn Any + Send + Sync + 'static>>>>>>;
13
14static STATE: State = LazyLock::new(Arc::default);
15
16#[derive(Debug)]
17pub struct InMemoryDataLayer {}
18impl InMemoryDataLayer {
19    pub(crate) fn new() -> Self {
20        Self {}
21    }
22}
23
24impl<R: Resource + 'static> DataLayer<R> for InMemoryDataLayer {
25    async fn create(&self, resource: R) -> crate::Result<R> {
26        let state = STATE.clone();
27        let mut state = state.write().await;
28
29        let map = state.entry(TypeId::of::<R>()).or_default();
30        map.insert(
31            resource.primary_key().to_string(),
32            Box::new(resource.clone()),
33        );
34
35        Ok(resource)
36    }
37
38    async fn read(&self, primary_key: &R::PrimaryKey) -> crate::Result<R> {
39        let state = STATE.clone();
40        let state = state.read().await;
41
42        let key = primary_key.to_string();
43
44        state
45            .get(&TypeId::of::<R>())
46            .and_then(|map| map.get(&key))
47            .and_then(|boxed| boxed.downcast_ref::<R>())
48            .cloned()
49            .ok_or_else(|| format!("resource not found for primary key `{key}`").into())
50    }
51
52    async fn update(&self, resource: R) -> crate::Result<()> {
53        let state = STATE.clone();
54        let mut state = state.write().await;
55
56        let key = resource.primary_key().to_string();
57
58        let map = state
59            .get_mut(&TypeId::of::<R>())
60            .ok_or_else(|| format!("resource not found for primary key `{key}`"))?;
61
62        if !map.contains_key(&key) {
63            return Err(format!("resource not found for primary key `{key}`").into());
64        }
65
66        map.insert(key, Box::new(resource.clone()));
67
68        Ok(())
69    }
70
71    async fn destroy(&self, primary_key: &R::PrimaryKey) -> crate::Result<R> {
72        let state = STATE.clone();
73        let mut state = state.write().await;
74
75        let key = primary_key.to_string();
76
77        let map = state
78            .get_mut(&TypeId::of::<R>())
79            .ok_or_else(|| format!("resource not found for primary key `{key}`"))?;
80
81        let boxed = map
82            .remove(&key)
83            .ok_or_else(|| format!("resource not found for primary key `{key}`"))?;
84
85        boxed
86            .downcast::<R>()
87            .map(|r| *r)
88            .map_err(|_| "failed to downcast destroyed resource".into())
89    }
90}
91
92/// Filter trait for non-paged in-memory read actions. The generated
93/// `resource!` macro emits an impl for each non-paged read action.
94pub trait InMemoryReadAction: ReadAction {
95    fn filter(row: &Self::Output, args: &Self::Arguments) -> bool;
96}
97
98/// Filter trait for paged in-memory read actions. Same filter interface as
99/// [`InMemoryReadAction`], but applied to paged actions which additionally
100/// require `Arguments: Paged`.
101pub trait InMemoryPagedReadAction: ReadAction {
102    fn filter(row: &Self::Output, args: &Self::Arguments) -> bool;
103}
104
105/// Unified execution trait that bridges the filter-based traits
106/// ([`InMemoryReadAction`] and [`InMemoryPagedReadAction`]) to the
107/// framework's [`PerformRead`] trait.
108///
109/// The `resource!` macro generates explicit impls of this trait for each
110/// read action, delegating to the appropriate filter logic. A single
111/// blanket `PerformRead` impl then dispatches to this trait.
112pub trait InMemoryPerformRead: ReadAction {
113    fn execute(
114        all: impl Iterator<Item = Self::Output>,
115        args: &Self::Arguments,
116    ) -> Self::Response;
117}
118
119/// Single `PerformRead` impl for `InMemoryDataLayer` that delegates to
120/// `InMemoryPerformRead::execute`.
121impl<R, A> PerformRead<A> for InMemoryDataLayer
122where
123    R: Resource + 'static,
124    A: ReadAction<Output = R> + InMemoryPerformRead + 'static,
125{
126    async fn read(&self, args: &A::Arguments) -> crate::Result<A::Response> {
127        let state = STATE.clone();
128        let state = state.read().await;
129
130        let type_map = state.get(&TypeId::of::<R>());
131        let all = type_map
132            .iter()
133            .flat_map(|map| map.values())
134            .filter_map(|boxed| boxed.downcast_ref::<R>())
135            .cloned();
136
137        Ok(A::execute(all, args))
138    }
139}