1use std::io::Read;
4
5use flate2::read::GzDecoder;
6use flate2::write::GzEncoder;
7use flate2::Compression;
8use serde::de::DeserializeOwned;
9use serde::Deserialize;
10use serde::Deserializer;
11use serde::Serialize;
12use serde::Serializer;
13
14#[cfg(feature = "node")]
15use crate::error::Error;
16
17#[allow(dead_code)]
18pub fn serialize_forward<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
20where
21 T: Serialize,
22 S: Serializer,
23{
24 value.serialize(serializer)
25}
26
27#[allow(dead_code)]
28pub fn deserialize_forward<'de, T, D>(deserializer: D) -> Result<T, D::Error>
30where
31 T: Deserialize<'de>,
32 D: Deserializer<'de>,
33{
34 T::deserialize(deserializer)
35}
36
37#[allow(dead_code)]
38pub fn serialize_gzip<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
40where
41 T: Serialize,
42 S: Serializer,
43{
44 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
45 serde_json::to_writer(&mut encoder, value).map_err(serde::ser::Error::custom)?;
46 let compressed_data = encoder.finish().map_err(serde::ser::Error::custom)?;
47 serializer.serialize_bytes(&compressed_data)
48}
49
50#[allow(dead_code)]
51pub fn deserialize_gzip<'de, D, T>(deserializer: D) -> Result<T, D::Error>
53where
54 T: DeserializeOwned,
55 D: Deserializer<'de>,
56{
57 let bytes = Vec::<u8>::deserialize(deserializer)?;
58 let mut decoder = GzDecoder::new(&bytes[..]);
59 let mut decompressed_data = Vec::new();
60 decoder
61 .read_to_end(&mut decompressed_data)
62 .map_err(serde::de::Error::custom)?;
63 serde_json::from_slice(&decompressed_data).map_err(serde::de::Error::custom)
64}
65
66pub fn build_version() -> String {
68 let mut infos = vec![];
69 if let Some(version) = option_env!("CARGO_PKG_VERSION") {
70 infos.push(version);
71 };
72 if let Some(git_hash) = option_env!("GIT_SHORT_HASH") {
73 infos.push(git_hash);
74 }
75 infos.join("-")
76}
77
78#[cfg(feature = "node")]
80pub fn expand_home<P>(path: P) -> Result<std::path::PathBuf, Error>
81where P: AsRef<std::path::Path> {
82 let Ok(stripped) = path.as_ref().strip_prefix("~") else {
83 return Ok(path.as_ref().to_path_buf());
84 };
85
86 let Some(mut p) = home::home_dir() else {
87 return Err(Error::HomeDirError);
88 };
89
90 p.push(stripped);
91
92 Ok(p)
93}
94
95#[cfg(feature = "node")]
97pub fn ensure_parent_dir<P>(path: P) -> Result<(), Error>
98where P: AsRef<std::path::Path> {
99 let path = expand_home(path)?;
100 let parent = path.parent().ok_or(Error::ParentDirError)?;
101 if !parent.is_dir() {
102 std::fs::create_dir_all(parent).map_err(|e| Error::CreateFileError(e.to_string()))?;
103 };
104 Ok(())
105}
106
107#[cfg(feature = "node")]
108pub mod loader {
109 use async_trait::async_trait;
112 use reqwest::Url;
113 use serde::de::DeserializeOwned;
114
115 use crate::seed::Seed;
116
117 #[async_trait]
120 pub trait ResourceLoader {
121 async fn load(source: &str) -> anyhow::Result<Self>
123 where Self: Sized + DeserializeOwned {
124 let url = Url::parse(source).map_err(|e| anyhow::anyhow!("{}", e))?;
125
126 if let Ok(path) = url.to_file_path() {
127 let data = std::fs::read_to_string(path)
128 .map_err(|_| anyhow::anyhow!("Unable to read resource file"))?;
129
130 serde_json::from_str(&data).map_err(|e| anyhow::anyhow!("{}", e))
131 } else {
132 let resp = reqwest::get(source)
133 .await
134 .map_err(|_| anyhow::anyhow!("failed to get resource from {}", source))?;
135 resp.json()
136 .await
137 .map_err(|_| anyhow::anyhow!("failed to load resource from {}", source))
138 }
139 }
140 }
141
142 impl ResourceLoader for Seed {}
143}
144
145#[cfg(test)]
146#[cfg(feature = "node")]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_expand_home_with_tilde() {
152 let input = "~";
153 let mut expected = std::env::var("HOME").unwrap();
154 expected.push('/');
155 let result = expand_home(input).unwrap();
156 assert_eq!(result.to_str(), Some(expected.as_str()));
157 }
158
159 #[test]
160 fn test_expand_home_with_relative_path() {
161 let input = "~/path/to/file.txt";
162 let mut expected = std::env::var("HOME").unwrap();
163 expected.push_str("/path/to/file.txt");
164 let result = expand_home(input).unwrap();
165 assert_eq!(result.to_str(), Some(expected.as_str()));
166 }
167
168 #[test]
169 fn test_expand_home_with_absolute_path() {
170 let input = "/absolute/path/to/file.txt";
171 let expected = std::path::PathBuf::from(input);
172 let result = expand_home(input).unwrap();
173 assert_eq!(result, expected);
174 }
175
176 #[test]
177 fn test_expand_home_with_invalid_path() {
178 let input = "path/does/not/exist.txt";
179 let expected = std::path::PathBuf::from(input);
180 let result = expand_home(input).unwrap();
181 assert_eq!(result, expected);
182 }
183
184 #[test]
185 fn test_expand_home_with_empty_path() {
186 let input = "";
187 let expected = std::path::PathBuf::from("");
188 let result = expand_home(input).unwrap();
189 assert_eq!(result, expected);
190 }
191}