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;
11use crate::core::dir::{default_config_dir, default_runtime_app_name};
12#[cfg(all(windows, feature = "uwp"))]
13use crate::core::dir::uwp_local_folder_dir;
14use crate::core::json::{JsonFormat, deserialize, serialize};
15use crate::core::validation::{validate_path_component, validate_plain_file_name};
16
17pub mod constant;
18mod dir;
19pub mod error;
20mod json;
21pub mod validation;
22
23#[cfg(test)]
24mod tests;
25
26#[derive(Debug, Clone)]
37pub struct ConfigManager<T> {
38 folder_path: PathBuf,
39 file_name: String,
40 json_format: JsonFormat,
41 _marker: PhantomData<T>,
42}
43
44impl<T> ConfigManager<T>
45where
46 T: Serialize + DeserializeOwned,
47{
48 pub fn new() -> Self {
59 Self::from_parts(default_config_dir().join(default_runtime_app_name()), DEFAULT_FILE_NAME)
60 }
61
62 pub fn for_app(app_name: &str) -> Result<Self> {
68 let app_name = validate_path_component(app_name)?;
69 Ok(Self::from_parts(
70 default_config_dir().join(app_name),
71 DEFAULT_FILE_NAME,
72 ))
73 }
74
75 fn from_parts<P>(folder_path: P, file_name: &str) -> Self
76 where
77 P: Into<PathBuf>,
78 {
79 Self {
80 folder_path: folder_path.into(),
81 file_name: file_name.to_string(),
82 json_format: JsonFormat::Pretty,
83 _marker: PhantomData,
84 }
85 }
86
87 pub fn at_current_dir(mut self) -> Self {
89 self.folder_path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
90 self
91 }
92
93 pub fn with_root_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
99 self.folder_path = path.into();
100 self
101 }
102
103 pub fn at_custom_dir<P: Into<PathBuf>>(self, path: P) -> Self {
108 self.with_root_dir(path)
109 }
110
111 #[cfg(all(windows, feature = "uwp"))]
116 pub fn at_uwp_local_folder(mut self) -> Result<Self> {
117 self.folder_path = uwp_local_folder_dir()?;
118 Ok(self)
119 }
120
121 pub fn with_filename(mut self, name: &str) -> Self {
127 self.file_name = name.to_string();
128 self
129 }
130
131 pub fn try_with_filename(mut self, name: &str) -> Result<Self> {
136 self.file_name = validate_plain_file_name(name)?.to_string();
137 Ok(self)
138 }
139
140 pub fn disable_pretty_json(mut self) -> Self {
142 self.json_format = JsonFormat::Compact;
143 self
144 }
145
146 pub fn folder_path(&self) -> &Path {
148 &self.folder_path
149 }
150
151 pub fn file_name(&self) -> &str {
153 &self.file_name
154 }
155
156 pub fn path(&self) -> PathBuf {
158 self.folder_path.join(&self.file_name)
159 }
160
161 pub fn save(&self, config: &T) -> Result<()> {
163 fs::create_dir_all(&self.folder_path)?;
164 fs::write(self.path(), serialize(config, self.json_format)?)?;
165 Ok(())
166 }
167
168 pub fn load(&self) -> Result<T> {
170 let content = fs::read_to_string(self.path())?;
171 deserialize(&content)
172 }
173}
174
175impl<T> Default for ConfigManager<T>
176where
177 T: Serialize + DeserializeOwned,
178{
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl<T> ConfigManager<T>
185where
186 T: Serialize + DeserializeOwned + Default,
187{
188 pub fn load_or_default(&self) -> Result<T> {
190 let path = self.path();
191
192 match fs::read_to_string(&path) {
193 Ok(content) => deserialize(&content),
194
195 Err(e) if e.kind() == io::ErrorKind::NotFound => {
196 let default_config = T::default();
197 self.save(&default_config)?;
198 Ok(default_config)
199 }
200
201 Err(e) => Err(ConfigError::Io(e)),
202 }
203 }
204
205 pub fn update<F>(&self, f: F) -> Result<T>
207 where
208 F: FnOnce(&mut T),
209 {
210 let mut cfg = self.load_or_default()?;
211 f(&mut cfg);
212 self.save(&cfg)?;
213 Ok(cfg)
214 }
215}