architect_api/utils/
maybe_file.rs

1#[cfg(feature = "tokio")]
2use anyhow::bail;
3use anyhow::Result;
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5use std::{fmt::Display, path::PathBuf};
6
7/// A type that is either a file containing the value or the value itself.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum MaybeFile<T> {
10    Value(T),
11    File(PathBuf),
12}
13
14impl<T: Display> Display for MaybeFile<T> {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            MaybeFile::Value(v) => write!(f, "{}", v),
18            MaybeFile::File(p) => write!(f, "{}", p.display()),
19        }
20    }
21}
22
23impl<T: Serialize> Serialize for MaybeFile<T> {
24    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25    where
26        S: serde::Serializer,
27    {
28        match self {
29            MaybeFile::Value(v) => v.serialize(serializer),
30            MaybeFile::File(p) => p.serialize(serializer),
31        }
32    }
33}
34
35impl<T: Clone + DeserializeOwned> MaybeFile<T> {
36    #[cfg(feature = "tokio")]
37    pub async fn load(&self) -> Result<T> {
38        match self {
39            MaybeFile::Value(v) => Ok(v.clone()),
40            MaybeFile::File(p) => {
41                let contents = tokio::fs::read_to_string(p).await?;
42                let t = match p.extension().and_then(std::ffi::OsStr::to_str) {
43                    Some("json") => serde_json::from_str(&contents)?,
44                    Some("yml") | Some("yaml") => serde_yaml::from_str(&contents)?,
45                    Some(other) => bail!("unknown file extension: {}", other),
46                    None => bail!("no file extension"),
47                };
48                Ok(t)
49            }
50        }
51    }
52}
53
54impl<'de, T: DeserializeOwned> Deserialize<'de> for MaybeFile<T> {
55    fn deserialize<D>(de: D) -> Result<Self, D::Error>
56    where
57        D: serde::Deserializer<'de>,
58    {
59        #[derive(Serialize, Deserialize)]
60        #[serde(untagged)]
61        enum Format<T> {
62            Value(T),
63            File(String),
64        }
65        match Format::<T>::deserialize(de)? {
66            Format::Value(t) => Ok(MaybeFile::Value(t)),
67            Format::File(s) => Ok(MaybeFile::File(PathBuf::from(s))),
68        }
69    }
70}