1use std::{
2 borrow::Cow,
3 path::{Path, PathBuf},
4};
5
6use serde::de;
7
8use crate::source::{utils, Any, Expansion, Source};
9
10pub struct FileSource {
52 base_path: PathBuf,
53 variable: utils::Variable,
54}
55
56impl FileSource {
57 pub fn new() -> Self {
78 Self {
79 base_path: PathBuf::new(),
80 variable: Default::default(),
81 }
82 }
83
84 pub fn with_base_path<P>(mut self, path: P) -> Self
91 where
92 P: Into<PathBuf>,
93 {
94 self.base_path = path.into();
95 self
96 }
97
98 pub fn with_variable_prefix(mut self, prefix: impl Into<String>) -> Self {
116 self.variable.prefix = prefix.into();
117 self
118 }
119
120 pub fn with_variable_suffix(mut self, suffix: impl Into<String>) -> Self {
122 self.variable.suffix = suffix.into();
123 self
124 }
125}
126
127impl FileSource {
128 fn resolve_path<'a>(&self, path: &'a Path) -> Cow<'a, Path> {
129 match path.is_absolute() {
130 true => Cow::Borrowed(path),
131 false => Cow::Owned(self.base_path.join(path)),
132 }
133 }
134
135 fn io_error<E>(&self, path: &Path, v: &Path, error: std::io::Error) -> E
136 where
137 E: de::Error,
138 {
139 let path = path.display();
140 let var = self.variable.fmt(v.display());
141 E::custom(format!(
142 "failed to read file `{path}` from variable `{var}`: {error}"
143 ))
144 }
145
146 fn mismatched_type<E>(&self, var: &str, unexpected: de::Unexpected<'_>, expected: &str) -> E
147 where
148 E: de::Error,
149 {
150 let var = self.variable.fmt(var);
151 E::invalid_value(
152 unexpected,
153 &format!("file contents of variable `{var}` to be {expected}").as_str(),
154 )
155 }
156
157 fn parsed<V, E>(&mut self, v: &str, expected: &str) -> Result<Option<V>, E>
158 where
159 V: std::str::FromStr,
160 V::Err: std::fmt::Display,
161 E: de::Error,
162 {
163 let Some(var) = self.variable.parse_str(v) else {
164 return Ok(None);
165 };
166
167 let path = self.resolve_path(var.as_ref());
168 let value = std::fs::read_to_string(&path)
169 .map_err(|error| self.io_error(&path, var.as_ref(), error))?;
170
171 value
172 .parse()
173 .map(Some)
174 .map_err(|_| self.mismatched_type(var, de::Unexpected::Str(&value), expected))
175 }
176}
177
178impl Source for FileSource {
179 fn expand_str<'a, E>(&mut self, v: Cow<'a, str>) -> Result<Expansion<Cow<'a, str>>, E>
180 where
181 E: serde::de::Error,
182 {
183 let Some(var) = self.variable.parse_str(&v) else {
184 return Ok(Expansion::Original(v));
185 };
186
187 let path = self.resolve_path(var.as_ref());
188 let value = std::fs::read_to_string(&path)
189 .map_err(|error| self.io_error(&path, var.as_ref(), error))?;
190
191 match utils::parse(Cow::Owned(value)) {
192 Any::Str(value) => Ok(Expansion::Expanded(value)),
193 other => Err(self.mismatched_type(var, other.unexpected(), "a string")),
194 }
195 }
196
197 fn expand_bytes<'a, E>(&mut self, v: Cow<'a, [u8]>) -> Result<Expansion<Cow<'a, [u8]>>, E>
198 where
199 E: serde::de::Error,
200 {
201 let Some(var) = self.variable.parse_bytes(&v) else {
202 return Ok(Expansion::Original(v));
203 };
204
205 #[cfg(unix)]
206 let path = {
207 use std::{ffi::OsStr, os::unix::ffi::OsStrExt, path::Path};
208 Path::new(OsStr::from_bytes(var))
209 };
210 #[cfg(not(unix))]
214 let path = std::str::from_utf8(var).map(Path::new).map_err(E::custom)?;
215
216 let full_path = self.resolve_path(path);
217 let value =
218 std::fs::read(&full_path).map_err(|error| self.io_error(&full_path, path, error))?;
219
220 Ok(Expansion::Expanded(Cow::Owned(value)))
221 }
222
223 fn expand_bool<E>(&mut self, v: &str) -> Result<Option<bool>, E>
224 where
225 E: de::Error,
226 {
227 self.parsed(v, "a boolean")
228 }
229
230 fn expand_i8<E>(&mut self, v: &str) -> Result<Option<i8>, E>
231 where
232 E: de::Error,
233 {
234 self.parsed(v, "a signed integer (i8)")
235 }
236
237 fn expand_i16<E>(&mut self, v: &str) -> Result<Option<i16>, E>
238 where
239 E: de::Error,
240 {
241 self.parsed(v, "a signed integer (i16)")
242 }
243
244 fn expand_i32<E>(&mut self, v: &str) -> Result<Option<i32>, E>
245 where
246 E: de::Error,
247 {
248 self.parsed(v, "a signed integer (i32)")
249 }
250
251 fn expand_i64<E>(&mut self, v: &str) -> Result<Option<i64>, E>
252 where
253 E: de::Error,
254 {
255 self.parsed(v, "a signed integer (i64)")
256 }
257
258 fn expand_u8<E>(&mut self, v: &str) -> Result<Option<u8>, E>
259 where
260 E: de::Error,
261 {
262 self.parsed(v, "an unsigned integer (i8)")
263 }
264
265 fn expand_u16<E>(&mut self, v: &str) -> Result<Option<u16>, E>
266 where
267 E: de::Error,
268 {
269 self.parsed(v, "an unsigned integer (i16)")
270 }
271
272 fn expand_u32<E>(&mut self, v: &str) -> Result<Option<u32>, E>
273 where
274 E: de::Error,
275 {
276 self.parsed(v, "an unsigned integer (i32)")
277 }
278
279 fn expand_u64<E>(&mut self, v: &str) -> Result<Option<u64>, E>
280 where
281 E: de::Error,
282 {
283 self.parsed(v, "an unsigned integer (i64)")
284 }
285
286 fn expand_f32<E>(&mut self, v: &str) -> Result<Option<f32>, E>
287 where
288 E: de::Error,
289 {
290 self.parsed(v, "a floating point")
291 }
292
293 fn expand_f64<E>(&mut self, v: &str) -> Result<Option<f64>, E>
294 where
295 E: de::Error,
296 {
297 self.parsed(v, "a floating point")
298 }
299
300 fn expand_any<'a, E>(&mut self, v: Cow<'a, str>) -> Result<Expansion<Any<'a>, Cow<'a, str>>, E>
301 where
302 E: de::Error,
303 {
304 let Some(var) = self.variable.parse_str(&v) else {
305 return Ok(Expansion::Original(v));
306 };
307
308 let path = self.resolve_path(var.as_ref());
309 let value =
310 std::fs::read(&path).map_err(|error| self.io_error(&path, var.as_ref(), error))?;
311
312 let value = String::from_utf8(value)
313 .map(Cow::Owned)
314 .map(utils::parse)
315 .unwrap_or_else(|err| Any::Bytes(Cow::Owned(err.into_bytes())));
316 Ok(Expansion::Expanded(value))
317 }
318}
319
320impl Default for FileSource {
321 fn default() -> Self {
322 Self::new()
323 }
324}