1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
use async_trait::async_trait;
use ron;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
env::current_dir,
io::Error,
path::{Path, PathBuf},
};
use tokio::{fs, io::AsyncReadExt};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConfigFormat {
Yaml,
Toml,
Ron,
Json,
}
impl ConfigFormat {
fn from_filename(filename: &str) -> Option<Self> {
if filename.ends_with(".yaml") || filename.ends_with(".yml") {
Some(Self::Yaml)
} else if filename.ends_with(".toml") || filename.ends_with(".tom") {
Some(Self::Toml)
} else if filename.ends_with(".ron") {
Some(Self::Ron)
} else if filename.ends_with(".json") {
Some(Self::Json)
} else {
None
}
}
}
#[async_trait]
pub trait ConfigFile: Serialize + for<'a> Deserialize<'a> + Default {
type DataType: Serialize + for<'a> Deserialize<'a> + Default + Send + Sync;
fn default_path() -> Result<PathBuf, Error>;
async fn read() -> Result<Self::DataType, std::io::Error>
where
Self: Sized + Send + Sync,
{
let path = Self::default_path()?;
Self::read_from(path).await
}
async fn read_from(path: impl AsRef<Path> + Send) -> Result<Self::DataType, std::io::Error>
where
Self: Sized + Send + Sync,
{
let path = path.as_ref();
let cwd = current_dir()?;
let file_path = cwd.join(path);
// Check if file exists
if fs::metadata(&file_path).await.is_err() {
return Ok(Self::DataType::default());
}
// Open file
let mut file = fs::File::open(&file_path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
// Determine file format
let format = file_path
.file_name()
.and_then(|name| name.to_str())
.and_then(ConfigFormat::from_filename)
.unwrap_or(ConfigFormat::Json); // Default to JSON
// Deserialize based on format
let result = match format {
ConfigFormat::Yaml => serde_yaml::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
ConfigFormat::Toml => toml::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
ConfigFormat::Ron => ron::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
ConfigFormat::Json => serde_json::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
};
Ok(result)
}
async fn write(val: &Self::DataType) -> Result<(), std::io::Error>
where
Self: Sized + Send + Sync,
{
let path = Self::default_path()?;
Self::write_to(val, path).await
}
async fn write_to(
val: &Self::DataType,
path: impl AsRef<Path> + Send,
) -> Result<(), std::io::Error>
where
Self: Sized + Send + Sync,
{
let path = path.as_ref();
if let Some(parent) = path.parent()
&& !parent.exists()
{
tokio::fs::create_dir_all(parent).await?;
}
let cwd = current_dir()?;
let file_path = cwd.join(path);
// Determine file format
let format = file_path
.file_name()
.and_then(|name| name.to_str())
.and_then(ConfigFormat::from_filename)
.unwrap_or(ConfigFormat::Json); // Default to JSON
let contents = match format {
ConfigFormat::Yaml => serde_yaml::to_string(val)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
ConfigFormat::Toml => toml::to_string(val)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
ConfigFormat::Ron => {
let mut pretty_config = ron::ser::PrettyConfig::new();
pretty_config.new_line = Cow::from("\n");
pretty_config.indentor = Cow::from(" ");
ron::ser::to_string_pretty(val, pretty_config)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
}
ConfigFormat::Json => serde_json::to_string(val)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
};
// Write to file
fs::write(&file_path, contents).await?;
Ok(())
}
/// Check if the file returned by `default_path` exists
fn exist() -> bool
where
Self: Sized + Send + Sync,
{
let Ok(path) = Self::default_path() else {
return false;
};
path.exists()
}
}
|