app_json_settings/
core.rs1use serde::{Serialize, de::DeserializeOwned};
2
3use std::fs;
4use std::io;
5use std::marker::PhantomData;
6use std::path::{Path, PathBuf};
7
8use crate::ConfigError;
9use crate::Result;
10use crate::core::constant::DEFAULT_FILE_NAME;
11#[cfg(all(windows, feature = "uwp"))]
12use crate::core::dir::uwp_local_folder_dir;
13use crate::core::dir::{default_config_dir, default_runtime_app_name};
14use crate::core::json::{JsonFormat, deserialize, serialize};
15use crate::core::save::save_to_path;
16use crate::core::validation::{validate_path_component, validate_plain_file_name};
17
18pub mod constant;
19mod dir;
20pub mod error;
21mod json;
22mod save;
23pub mod validation;
24
25pub use save::SaveMode;
26
27#[cfg(test)]
28mod tests;
29
30#[derive(Debug, Clone)]
42pub struct ConfigManager<T> {
43 folder_path: PathBuf,
44 file_name: String,
45 json_format: JsonFormat,
46 save_mode: SaveMode,
47 _marker: PhantomData<T>,
48}
49
50impl<T> ConfigManager<T>
51where
52 T: Serialize + DeserializeOwned,
53{
54 pub fn new() -> Self {
65 Self::from_parts(
66 default_config_dir().join(default_runtime_app_name()),
67 DEFAULT_FILE_NAME,
68 )
69 }
70
71 pub fn for_app(app_name: &str) -> Result<Self> {
77 let app_name = validate_path_component(app_name)?;
78 Ok(Self::from_parts(
79 default_config_dir().join(app_name),
80 DEFAULT_FILE_NAME,
81 ))
82 }
83
84 fn from_parts<P>(folder_path: P, file_name: &str) -> Self
85 where
86 P: Into<PathBuf>,
87 {
88 Self {
89 folder_path: folder_path.into(),
90 file_name: file_name.to_string(),
91 json_format: JsonFormat::Pretty,
92 save_mode: SaveMode::Atomic,
93 _marker: PhantomData,
94 }
95 }
96
97 pub fn at_current_dir(mut self) -> Self {
99 self.folder_path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
100 self
101 }
102
103 pub fn with_root_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
109 self.folder_path = path.into();
110 self
111 }
112
113 pub fn at_custom_dir<P: Into<PathBuf>>(self, path: P) -> Self {
118 self.with_root_dir(path)
119 }
120
121 #[cfg(all(windows, feature = "uwp"))]
126 pub fn at_uwp_local_folder(mut self) -> Result<Self> {
127 self.folder_path = uwp_local_folder_dir()?;
128 Ok(self)
129 }
130
131 pub fn with_filename(mut self, name: &str) -> Self {
137 self.file_name = name.to_string();
138 self
139 }
140
141 pub fn try_with_filename(mut self, name: &str) -> Result<Self> {
146 self.file_name = validate_plain_file_name(name)?.to_string();
147 Ok(self)
148 }
149
150 pub fn disable_pretty_json(mut self) -> Self {
152 self.json_format = JsonFormat::Compact;
153 self
154 }
155
156 pub fn with_save_mode(mut self, mode: SaveMode) -> Self {
162 self.save_mode = mode;
163 self
164 }
165
166 pub fn with_direct_save(self) -> Self {
170 self.with_save_mode(SaveMode::Direct)
171 }
172
173 pub fn save_mode(&self) -> SaveMode {
175 self.save_mode
176 }
177
178 pub fn folder_path(&self) -> &Path {
180 &self.folder_path
181 }
182
183 pub fn file_name(&self) -> &str {
185 &self.file_name
186 }
187
188 pub fn path(&self) -> PathBuf {
190 self.folder_path.join(&self.file_name)
191 }
192
193 pub fn save(&self, config: &T) -> Result<()> {
195 let content = serialize(config, self.json_format)?;
196 save_to_path(&self.path(), &content, self.save_mode)
197 }
198
199 pub fn load(&self) -> Result<T> {
201 let content = fs::read_to_string(self.path())?;
202 deserialize(&content)
203 }
204}
205
206impl<T> Default for ConfigManager<T>
207where
208 T: Serialize + DeserializeOwned,
209{
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl<T> ConfigManager<T>
216where
217 T: Serialize + DeserializeOwned + Default,
218{
219 pub fn load_or_default(&self) -> Result<T> {
221 let path = self.path();
222
223 match fs::read_to_string(&path) {
224 Ok(content) => deserialize(&content),
225
226 Err(e) if e.kind() == io::ErrorKind::NotFound => {
227 let default_config = T::default();
228 self.save(&default_config)?;
229 Ok(default_config)
230 }
231
232 Err(e) => Err(ConfigError::Io(e)),
233 }
234 }
235
236 pub fn update<F>(&self, f: F) -> Result<T>
238 where
239 F: FnOnce(&mut T),
240 {
241 let mut cfg = self.load_or_default()?;
242 f(&mut cfg);
243 self.save(&cfg)?;
244 Ok(cfg)
245 }
246}