Experiments in applying Entity-Component-System patterns to durable data storage APIs.
at main 7.0 kB view raw
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);