#[cfg(feature = "tokio")]
use anyhow::bail;
use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{fmt::Display, path::PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaybeFile<T> {
Value(T),
File(PathBuf),
}
impl<T: Display> Display for MaybeFile<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MaybeFile::Value(v) => write!(f, "{}", v),
MaybeFile::File(p) => write!(f, "{}", p.display()),
}
}
}
impl<T: Serialize> Serialize for MaybeFile<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MaybeFile::Value(v) => v.serialize(serializer),
MaybeFile::File(p) => p.serialize(serializer),
}
}
}
impl<T: Clone + DeserializeOwned> MaybeFile<T> {
#[cfg(feature = "tokio")]
pub async fn load(&self) -> Result<T> {
match self {
MaybeFile::Value(v) => Ok(v.clone()),
MaybeFile::File(p) => {
let contents = tokio::fs::read_to_string(p).await?;
let t = match p.extension().and_then(std::ffi::OsStr::to_str) {
Some("json") => serde_json::from_str(&contents)?,
Some("yml") | Some("yaml") => serde_yaml::from_str(&contents)?,
Some(other) => bail!("unknown file extension: {}", other),
None => bail!("no file extension"),
};
Ok(t)
}
}
}
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for MaybeFile<T> {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Format<T> {
Value(T),
File(String),
}
match Format::<T>::deserialize(de)? {
Format::Value(t) => Ok(MaybeFile::Value(t)),
Format::File(s) => Ok(MaybeFile::File(PathBuf::from(s))),
}
}
}