1use serde::{Serialize, de::DeserializeOwned};
32use std::{
33 fs::{self, OpenOptions},
34 io::Write,
35 path::Path,
36};
37use thiserror::Error;
38
39#[derive(Debug, Error)]
40pub enum Error {
41 #[error("IO error: {0}")]
42 IoE(#[from] std::io::Error),
43 #[error("serde error: {0}")]
44 JsonE(#[from] serde_json::Error),
45 #[error("parse toml error: {0}")]
46 ParTomlE(#[from] toml::ser::Error),
47 #[error("parse toml error: {0}")]
48 DesTomlE(#[from] toml::de::Error),
49 #[error("extension does not exist.")]
50 ExtensionDoesNotExist,
51}
52
53pub enum Format {
54 Json,
55 Toml,
56}
57
58fn path_to_format<P: AsRef<Path>>(path: P) -> Result<Format, Error> {
59 if let Some(v) = path.as_ref().extension().and_then(|f| f.to_str()) {
60 match v {
61 "json" => Ok(Format::Json),
62 "toml" => Ok(Format::Toml),
63 _ => Err(Error::ExtensionDoesNotExist),
64 }
65 } else {
66 Err(Error::ExtensionDoesNotExist)
67 }
68}
69
70pub trait Storeable: Serialize + DeserializeOwned + Sized {
71 fn save<P: AsRef<Path>>(&self, path: P, new_create: bool, format: Format) -> Result<(), Error> {
81 let s = match format {
82 Format::Json => serde_json::to_string_pretty(self)?,
83 Format::Toml => toml::to_string_pretty(self)?,
84 };
85
86 path.as_ref().parent().map(fs::create_dir_all).transpose()?;
87
88 let mut f = OpenOptions::new()
89 .write(true)
90 .truncate(true)
91 .create(new_create)
92 .open(path)?;
93
94 f.write_all(s.as_bytes())?;
95 Ok(())
96 }
97
98 fn save_by_extension<P: AsRef<Path>>(&self, path: P, new_create: bool) -> Result<(), Error> {
109 let format = path_to_format(&path)?;
110 self.save(path, new_create, format)
111 }
112
113 fn load<P: AsRef<Path>>(path: P, format: Format) -> Result<Self, Error> {
122 let content = std::fs::read_to_string(path)?;
123 Ok(match format {
125 Format::Json => serde_json::from_str::<Self>(&content)?,
126 Format::Toml => toml::from_str::<Self>(&content)?,
127 })
128 }
129
130 fn load_by_extension<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
141 let format = path_to_format(&path)?;
142 Self::load(path, format)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use crate::Storeable;
149 use serde::{Deserialize, Serialize};
150 use std::path::PathBuf;
151
152 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
153 struct User {
154 name: String,
155 email: String,
156 }
157
158 impl Storeable for User {}
159
160 fn ready_test_env() -> PathBuf {
161 let test_path = std::env::current_dir().unwrap().join("test");
162 if !test_path.exists() {
163 std::fs::create_dir_all(&test_path).unwrap();
164 }
165 test_path
166 }
167
168 #[test]
169 fn save_test() {
170 let test_f_path = ready_test_env();
171 let save_path = test_f_path.join("user.toml");
172 let user = User {
173 name: "Alice".to_string(),
174 email: "alice@alice.com".to_string(),
175 };
176 let res = user.save_by_extension(save_path, true);
177 if let Err(e) = &res {
178 eprintln!("{e}");
179 }
180 assert!(res.is_ok());
181 }
182
183 #[test]
184 fn load_test() {
185 let test_f_path = ready_test_env();
186 let save_path = test_f_path.join("user.toml");
187 let user = User {
188 name: "Alice".to_string(),
189 email: "alice@alice.com".to_string(),
190 };
191
192 let _ = dbg!(user.save_by_extension(&save_path, true));
193
194 let loaded = User::load_by_extension(save_path);
195 match loaded {
196 Ok(v) => assert_eq!(v, user),
197 Err(_) => assert!(loaded.is_ok()),
198 }
199 }
200}