use std::path::Path;
use crate::{
descriptor::{ConfigValueDescriptor, VarDescriptor},
error::ReadVarError,
layer::Layer,
};
pub struct FileRead<V> {
pub(crate) var: V,
}
impl<V> ConfigValueDescriptor for FileRead<V>
where
V: ConfigValueDescriptor,
{
#[inline]
fn get_descriptor(&self) -> &VarDescriptor {
self.var.get_descriptor()
}
}
impl<V> Layer for FileRead<V>
where
V: Layer,
<V as Layer>::Output: AsRef<Path>,
ReadVarError: From<<V as Layer>::Error>,
{
type Output = String;
type Error = ReadVarError;
fn try_get(&self) -> Result<Self::Output, Self::Error> {
let path = self.var.try_get()?;
std::fs::read_to_string(path.as_ref()).map_err(|e| ReadVarError::Other(Box::new(e)))
}
}
#[cfg(test)]
mod tests {
use std::{error::Error, fs, io, path::Path};
use crate::{
error::{ParseError, ReadVarError},
prelude::*,
tests::{assert_matches, with_env},
};
fn with_file<F, R, P, C>(file_path: P, file_content: C, f: F) -> R
where
F: FnOnce() -> R,
P: AsRef<Path>,
C: AsRef<[u8]>,
{
fs::write(&file_path, file_content).unwrap();
let r = f();
fs::remove_file(file_path).unwrap();
r
}
#[test]
fn assert_not_found() {
const VAR_NAME: &str = "__TEST_FILE_NOT_FOUND";
let config = TextVar::from_var_name(VAR_NAME).file_read();
fn is_not_found_err(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<io::Error>()
.filter(|e| matches!(e.kind(), io::ErrorKind::NotFound))
.is_some()
}
let res = with_env([(VAR_NAME, "./__test_file_not_found")], || config.try_get());
assert_matches!(
res,
Err(ReadVarError::Other(e)) if is_not_found_err(&*e)
);
}
#[test]
fn assert_file_content() {
const VAR_NAME: &str = "__TEST_FILE_CONTENT";
const FILE_PATH: &str = "./__test_file_content";
let config = TextVar::from_var_name(VAR_NAME).file_read();
let res = with_file(FILE_PATH, "hello there", || {
with_env([(VAR_NAME, FILE_PATH)], || config.try_get())
});
assert_matches!(res.as_deref(), Ok("hello there"));
}
#[test]
fn assert_parsed_content() {
const VAR_NAME: &str = "__TEST_PARSED_FILE_CONTENT";
const FILE_PATH: &str = "./__test_parsed_file_content";
let config = TextVar::from_var_name(VAR_NAME)
.file_read()
.parsed_from_str::<i32>();
let res = with_file(FILE_PATH, "3", || {
with_env([(VAR_NAME, FILE_PATH)], || config.try_get())
});
assert_matches!(res, Ok(3));
}
#[test]
fn assert_file_content_parse_error() {
const VAR_NAME: &str = "__TEST_FILE_CONTENT_PARSE_ERROR";
const FILE_PATH: &str = "./__test_file_content_parse_ERROR";
let config = TextVar::from_var_name(VAR_NAME)
.file_read()
.parsed_from_str::<i32>();
fn is_parse_error(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<ParseError>().is_some()
}
let res = with_file(FILE_PATH, "foobar", || {
with_env([(VAR_NAME, FILE_PATH)], || config.try_get())
});
assert_matches!(res, Err(ReadVarError::Other(e)) if is_parse_error(&*e));
}
#[test]
fn assert_parsed_path() {
const VAR_NAME: &str = "__TEST_PARSED_FILE_PATH";
const FILE_PATH: &str = "./__test_parsed_file_path";
let config = TextVar::from_var_name(VAR_NAME)
.parsed(|input| Ok(input.split(":").nth(1).unwrap().to_owned()))
.file_read();
let res = with_file(FILE_PATH, "hello", || {
with_env([(VAR_NAME, format!("booga:{FILE_PATH}").as_str())], || {
config.try_get()
})
});
assert_matches!(res.as_deref(), Ok("hello"));
}
#[test]
fn assert_parsed_path_failed() {
const VAR_NAME: &str = "__TEST_PARSED_FILE_PATH_FAILED";
let config = TextVar::from_var_name(VAR_NAME)
.parsed(|input| {
input
.split(":")
.nth(1)
.ok_or_else(|| "this is bad".into())
.map(|s| s.to_owned())
})
.file_read();
fn is_parse_error(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<ParseError>()
.and_then(|e| e.source())
.filter(|s| s.to_string() == "this is bad")
.is_some()
}
let res = with_env([(VAR_NAME, "im not expected")], || config.try_get());
assert_matches!(res, Err(ReadVarError::Other(e)) if is_parse_error(&*e));
}
}