architect_api/utils/
maybe_file.rs1#[cfg(feature = "tokio")]
2use anyhow::bail;
3use anyhow::Result;
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5use std::{fmt::Display, path::PathBuf};
6
7#[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: Clone + DeserializeOwned> MaybeFile<T> {
24 #[cfg(feature = "tokio")]
25 pub async fn load(&self) -> Result<T> {
26 match self {
27 MaybeFile::Value(v) => Ok(v.clone()),
28 MaybeFile::File(p) => {
29 let contents = tokio::fs::read_to_string(p).await?;
30 let t = match p.extension().and_then(std::ffi::OsStr::to_str) {
31 Some("json") => serde_json::from_str(&contents)?,
32 Some("yml") | Some("yaml") => serde_yaml::from_str(&contents)?,
33 Some(other) => bail!("unknown file extension: {}", other),
34 None => bail!("no file extension"),
35 };
36 Ok(t)
37 }
38 }
39 }
40}
41
42impl<'de, T: DeserializeOwned> Deserialize<'de> for MaybeFile<T> {
43 fn deserialize<D>(de: D) -> Result<Self, D::Error>
44 where
45 D: serde::Deserializer<'de>,
46 {
47 #[derive(Serialize, Deserialize)]
48 #[serde(untagged)]
49 enum Format<T> {
50 Value(T),
51 File(String),
52 }
53 match Format::<T>::deserialize(de)? {
54 Format::Value(t) => Ok(MaybeFile::Value(t)),
55 Format::File(s) => Ok(MaybeFile::File(PathBuf::from(s))),
56 }
57 }
58}