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: 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}