use clap::{builder::TypedValueParser, error::ErrorKind};
use eyre::{eyre, Result};
use serde::de::DeserializeOwned;
#[derive(Debug, Clone)]
pub struct File<T> {
_p: std::marker::PhantomData<T>,
}
impl<T> Default for File<T> {
fn default() -> Self {
Self {
_p: std::marker::PhantomData,
}
}
}
impl<T> TypedValueParser for File<T>
where
T: DeserializeOwned + Sync + Send + Clone + 'static,
{
type Value = T;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let path = std::path::PathBuf::from(value);
let raw = std::fs::read_to_string(&path).map_err(|e| {
cmd.clone().error(
ErrorKind::InvalidValue,
format!(
"Could not read file {} for {}: {}",
value.to_str().unwrap(),
arg.unwrap(),
e
),
)
})?;
let content: Result<T> = match mime_guess::from_path(path.clone())
.first_or_text_plain()
.subtype()
.as_str()
{
"x-yaml" => serde_path_to_error::deserialize(serde_yaml::Deserializer::from_str(&raw))
.map_err(|e| eyre!(e)),
"json" => {
serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_str(&raw))
.map_err(|e| eyre!(e))
}
unsupported => Err(eyre!("Unsupported file type: {}", unsupported)),
};
content.map_err(|e| {
cmd.clone().error(
ErrorKind::InvalidValue,
format!(
"Failed to deserialize {} for {}: {}",
value.to_str().unwrap(),
arg.unwrap(),
e
),
)
})
}
}