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