Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1use std::any::Any;
2
3use serde::{Serialize, de::DeserializeOwned};
4
5pub use ecsdb_derive::{Bundle, Component};
6
7pub trait Component: Sized + Any + ComponentRead<Self> + ComponentWrite<Self> {
8 type Storage;
9
10 const NAME: &'static str;
11 fn component_name() -> &'static str {
12 Self::NAME
13 }
14}
15
16pub trait ComponentWrite<C> {
17 fn to_rusqlite<'a>(component: &'a C) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError>;
18}
19
20pub trait ComponentRead<C> {
21 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<C, StorageError>;
22}
23
24impl<C, S> ComponentRead<Self> for C
25where
26 C: Component<Storage = S>,
27 S: ComponentRead<C>,
28{
29 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<Self, StorageError> {
30 S::from_rusqlite(value)
31 }
32}
33
34impl<C, S> ComponentWrite<Self> for C
35where
36 C: Component<Storage = S>,
37 S: ComponentWrite<C>,
38{
39 fn to_rusqlite<'a>(
40 component: &'a Self,
41 ) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> {
42 S::to_rusqlite(component)
43 }
44}
45
46pub struct JsonStorage;
47
48#[derive(thiserror::Error, Debug)]
49#[error("Error reading/writing Component: {0}")]
50pub struct StorageError(String);
51
52impl<C> ComponentRead<C> for JsonStorage
53where
54 C: Component + DeserializeOwned,
55{
56 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<C, StorageError> {
57 let s = match value {
58 rusqlite::types::ToSqlOutput::Borrowed(rusqlite::types::ValueRef::Text(s)) => s,
59 rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Text(s)) => s.as_bytes(),
60 other => return Err(StorageError(format!("Unexpected type {other:?}"))),
61 };
62
63 serde_json::from_slice(s).map_err(|e| StorageError(e.to_string()))
64 }
65}
66
67impl<C> ComponentWrite<C> for JsonStorage
68where
69 C: Component + Serialize,
70{
71 fn to_rusqlite<'a>(component: &'a C) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> {
72 let json = serde_json::to_string(&component).map_err(|e| StorageError(e.to_string()))?;
73 Ok(rusqlite::types::ToSqlOutput::Owned(
74 rusqlite::types::Value::Text(json),
75 ))
76 }
77}
78
79pub struct BlobStorage;
80
81impl<C> ComponentRead<C> for BlobStorage
82where
83 C: Component + From<Vec<u8>>,
84{
85 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<C, StorageError> {
86 let b = match value {
87 rusqlite::types::ToSqlOutput::Borrowed(rusqlite::types::ValueRef::Blob(b)) => *b,
88 rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Blob(b)) => b,
89 other => return Err(StorageError(format!("Unexpected type {other:?}"))),
90 };
91
92 Ok(C::from(b.to_vec()))
93 }
94}
95
96impl<C> ComponentWrite<C> for BlobStorage
97where
98 C: Component + AsRef<[u8]>,
99{
100 fn to_rusqlite<'a>(component: &'a C) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> {
101 Ok(rusqlite::types::ToSqlOutput::Borrowed(
102 rusqlite::types::ValueRef::Blob(component.as_ref()),
103 ))
104 }
105}
106
107// impl<C> ComponentWrite<C> for BlobStorage
108// where
109// C: Component + Into<Vec<u8>>,
110// {
111// fn to_rusqlite<'a>(component: &'a C) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> {
112// Ok(rusqlite::types::ToSqlOutput::Owned(
113// rusqlite::types::Value::Blob(component.into().as_slice()),
114// ))
115// }
116// }
117
118pub struct NullStorage;
119
120impl<C> ComponentRead<C> for NullStorage
121where
122 C: Component + DeserializeOwned,
123{
124 fn from_rusqlite(value: &rusqlite::types::ToSqlOutput<'_>) -> Result<C, StorageError> {
125 match value {
126 rusqlite::types::ToSqlOutput::Borrowed(rusqlite::types::ValueRef::Null)
127 | rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Null) => {
128 serde_json::from_str("null").map_err(|e| StorageError(e.to_string()))
129 }
130 other => Err(StorageError(format!("Unexpected type {other:?}"))),
131 }
132 }
133}
134
135impl<C> ComponentWrite<C> for NullStorage
136where
137 C: Component + Serialize,
138{
139 fn to_rusqlite<'a>(
140 _component: &'a C,
141 ) -> Result<rusqlite::types::ToSqlOutput<'a>, StorageError> {
142 Ok(rusqlite::types::ToSqlOutput::Owned(
143 rusqlite::types::Value::Null,
144 ))
145 }
146}
147
148pub type BundleData<'a> = Vec<(&'static str, Option<rusqlite::types::ToSqlOutput<'a>>)>;
149pub type BundleDataRef<'a> = &'a [(&'static str, Option<rusqlite::types::ToSqlOutput<'a>>)];
150
151pub trait Bundle: Sized {
152 const COMPONENTS: &'static [&'static str];
153
154 fn component_names() -> &'static [&'static str] {
155 Self::COMPONENTS
156 }
157
158 fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError>;
159 // fn from_rusqlite<'a>(components: BundleDataRef<'a>) -> Result<Option<Self>, StorageError>;
160}
161
162pub trait BundleComponent {
163 const NAME: &'static str;
164 fn to_rusqlite<'a>(&'a self) -> Result<Option<rusqlite::types::ToSqlOutput<'a>>, StorageError>;
165}
166
167impl Bundle for () {
168 const COMPONENTS: &'static [&'static str] = &[];
169
170 fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError> {
171 Ok(vec![])
172 }
173}
174
175impl<C: Component> BundleComponent for C {
176 const NAME: &'static str = C::NAME;
177
178 fn to_rusqlite<'a>(&'a self) -> Result<Option<rusqlite::types::ToSqlOutput<'a>>, StorageError> {
179 Ok(Some(C::to_rusqlite(self)?))
180 }
181}
182
183impl<C: Component> BundleComponent for Option<C> {
184 const NAME: &'static str = C::NAME;
185
186 fn to_rusqlite<'a>(&'a self) -> Result<Option<rusqlite::types::ToSqlOutput<'a>>, StorageError> {
187 match self {
188 Some(c) => <C as BundleComponent>::to_rusqlite(c),
189 None => Ok(None),
190 }
191 }
192}
193
194impl<C: Component> Bundle for C {
195 const COMPONENTS: &'static [&'static str] = &[C::NAME];
196
197 fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError> {
198 Ok(vec![(C::NAME, Some(C::to_rusqlite(self)?))])
199 }
200}
201
202impl<C: Component> Bundle for Option<C> {
203 const COMPONENTS: &'static [&'static str] = &[C::NAME];
204
205 fn to_rusqlite<'a>(&'a self) -> Result<BundleData<'a>, StorageError> {
206 Ok(vec![(
207 C::NAME,
208 self.as_ref().map(C::to_rusqlite).transpose()?,
209 )])
210 }
211}
212
213macro_rules! bundle_tuples{
214 ($($ts:ident)*) => {
215 impl<$($ts,)+> Bundle for ($($ts,)+)
216 where
217 $($ts: BundleComponent,)+
218 {
219 const COMPONENTS: &'static [&'static str] = &[
220 $($ts::NAME,)+
221 ];
222
223 fn to_rusqlite<'a>(
224 &'a self
225 ) -> Result<BundleData<'a>, StorageError> {
226 #[allow(non_snake_case)]
227 let ($($ts,)+) = self;
228 Ok(
229 vec![
230 $(($ts::NAME, $ts::to_rusqlite($ts)?),)+
231 ]
232 )
233 }
234 }
235 }
236}
237
238crate::tuple_macros::for_each_tuple!(bundle_tuples);