Skip to main content

elemental_shared/
persistor.rs

1use crate::{
2    scope::Scope,
3    version::{Persistor, VersionControlled},
4};
5use anyhow::Result;
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8use std::{io::ErrorKind, marker::PhantomData, path::PathBuf};
9use tokio::fs::{read_to_string, write};
10
11pub struct NoPersistor;
12
13impl<V: VersionControlled> Persistor<V> for NoPersistor {
14    async fn load(&self) -> Result<Option<V>> {
15        Ok(Some(V::default()))
16    }
17
18    async fn save(&self, _value: &V) -> Result<()> {
19        Ok(())
20    }
21}
22
23pub trait PersistorIO<S> {
24    fn read(path: PathBuf) -> impl Future<Output = Result<Option<S>>>;
25    fn write(path: PathBuf, data: S) -> impl Future<Output = Result<()>>;
26}
27
28pub struct AsyncFileStringIO;
29
30impl PersistorIO<String> for AsyncFileStringIO {
31    async fn read(path: PathBuf) -> Result<Option<String>> {
32        match read_to_string(&path).await {
33            Ok(d) => Ok(Some(d)),
34            Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
35            Err(e) => Err(e.into()),
36        }
37    }
38
39    async fn write(path: PathBuf, data: String) -> Result<()> {
40        write(&path, data).await?;
41        Ok(())
42    }
43}
44
45pub struct CustomPersistor<
46    V: Serialize + DeserializeOwned,
47    S,
48    D: ?Sized,
49    IO: PersistorIO<S>,
50    Ser: Fn(&V) -> Result<S>,
51    De: Fn(&D) -> Result<V>,
52> {
53    pub ser: Ser,
54    pub de: De,
55    pub id: String,
56    pub scope: Scope,
57    pub suffix: Option<String>,
58    _marker: PhantomData<(IO, V, S, *const D)>,
59}
60
61impl<V, S, D, IO, Ser, De> CustomPersistor<V, S, D, IO, Ser, De>
62where
63    V: Serialize + DeserializeOwned,
64    D: ?Sized,
65    IO: PersistorIO<S>,
66    Ser: Fn(&V) -> Result<S>,
67    De: Fn(&D) -> Result<V>,
68    S: AsRef<D>,
69{
70    pub fn new(ser: Ser, de: De, id: String, scope: Scope, suffix: Option<String>) -> Self {
71        Self {
72            ser,
73            de,
74            id,
75            scope,
76            suffix,
77            _marker: PhantomData,
78        }
79    }
80}
81
82#[cfg(feature = "json")]
83type JsonFilePersistor<V> = CustomPersistor<
84    V,
85    String,
86    str,
87    AsyncFileStringIO,
88    fn(&V) -> Result<String>,
89    fn(&str) -> Result<V>,
90>;
91
92#[cfg(feature = "json")]
93fn serialize_json<V: Serialize>(value: &V) -> Result<String> {
94    Ok(serde_json::to_string(value)?)
95}
96
97#[cfg(feature = "json")]
98fn deserialize_json<V: DeserializeOwned>(value: &str) -> Result<V> {
99    Ok(serde_json::from_str(value)?)
100}
101
102impl<V, S, D, IO, Ser, De> Persistor<V> for CustomPersistor<V, S, D, IO, Ser, De>
103where
104    V: VersionControlled + Serialize + DeserializeOwned,
105    D: ?Sized,
106    IO: PersistorIO<S>,
107    Ser: Fn(&V) -> Result<S>,
108    De: Fn(&D) -> Result<V>,
109    S: AsRef<D>,
110{
111    async fn load(&self) -> Result<Option<V>> {
112        let path = self
113            .scope
114            .get_full_path(&self.id, self.suffix.clone())
115            .await?;
116        let data = IO::read(path).await?;
117        match data {
118            Some(d) => Ok(Some((self.de)(d.as_ref())?)),
119            None => Ok(None),
120        }
121    }
122
123    async fn save(&self, value: &V) -> Result<()> {
124        let data = (self.ser)(value)?;
125        let path = self
126            .scope
127            .get_full_path(&self.id, self.suffix.clone())
128            .await?;
129        IO::write(path, data).await?;
130        Ok(())
131    }
132}
133
134#[cfg(feature = "json")]
135pub fn json_persistor<V: Serialize + DeserializeOwned>(
136    id: String,
137    scope: Scope,
138) -> JsonFilePersistor<V> {
139    CustomPersistor::new(
140        serialize_json::<V>,
141        deserialize_json::<V>,
142        id,
143        scope,
144        Some("json".to_string()),
145    )
146}
147
148#[cfg(feature = "toml")]
149type TomlFilePersistor<V> = CustomPersistor<
150    V,
151    String,
152    str,
153    AsyncFileStringIO,
154    fn(&V) -> Result<String>,
155    fn(&str) -> Result<V>,
156>;
157
158#[cfg(feature = "toml")]
159fn serialize_toml<V: Serialize>(value: &V) -> Result<String> {
160    Ok(toml::to_string(value)?)
161}
162
163#[cfg(feature = "toml")]
164fn deserialize_toml<V: DeserializeOwned>(value: &str) -> Result<V> {
165    Ok(toml::from_str(value)?)
166}
167
168#[cfg(feature = "toml")]
169pub fn toml_persistor<V: Serialize + DeserializeOwned>(
170    id: String,
171    scope: Scope,
172) -> TomlFilePersistor<V> {
173    CustomPersistor::new(
174        serialize_toml::<V>,
175        deserialize_toml::<V>,
176        id,
177        scope,
178        Some("toml".to_string()),
179    )
180}