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}