use std::{collections::BTreeMap, marker::PhantomData, vec::IntoIter}; use serde::Deserialize; pub trait EnvEntryConfig { const SECRETS: &'static [&'static str]; const BAO_KEY: &'static str; } pub struct EnvEntry(Vec<(&'static str, String)>, PhantomData); impl EnvEntry { pub fn try_new_from_env() -> anyhow::Result { let mut result = Vec::with_capacity(T::SECRETS.len()); for key in T::SECRETS { let value = common::env::read_env(key)?; result.push((*key, value)); } Ok(Self(result, PhantomData)) } fn new_from_values(values: Vec<(&'static str, String)>) -> Self { Self(values, PhantomData) } pub fn read_from_bao() -> anyhow::Result { read_bao_data() } } impl From> for Vec<(&'static str, String)> { fn from(value: EnvEntry) -> Self { value.0 } } impl IntoIterator for EnvEntry { type Item = (&'static str, String); type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'de, T: EnvEntryConfig> serde::Deserialize<'de> for EnvEntry { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(EnvEntryVisitor(PhantomData)) } } struct EnvEntryVisitor(PhantomData); impl<'de, T: EnvEntryConfig> serde::de::Visitor<'de> for EnvEntryVisitor { type Value = EnvEntry; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_fmt(format_args!( "a map with unique keys {} with string values", T::SECRETS.join(", "), )) } fn visit_map(self, mut map: A) -> Result where A: serde::de::MapAccess<'de>, { let mut values = BTreeMap::<&'static str, String>::new(); while let Some((key, value)) = map.next_entry::<&'de str, String>()? { let mapped_key = T::SECRETS.iter().find(|n| **n == key).copied(); let Some(key) = mapped_key else { return Err(serde::de::Error::unknown_field(key, T::SECRETS)); }; if values.contains_key(key) { return Err(serde::de::Error::duplicate_field(key)); } values.insert(key, value); } for key in T::SECRETS { if !values.contains_key(key) { return Err(serde::de::Error::missing_field(key)); } } let values = values.into_iter().collect(); let entry = EnvEntry::::new_from_values(values); Ok(entry) } } #[derive(Debug, Deserialize)] struct OpenBaoKvEntry { data: OpenBaoKvEntryData, } #[derive(Debug, Deserialize)] struct OpenBaoKvEntryData { data: T, } fn read_bao_data() -> anyhow::Result> { let mut cmd = common::proc::Command::new("bao"); cmd.args(["kv", "get", "-format=json", "-mount=opentofu", T::BAO_KEY]); let result: OpenBaoKvEntry> = cmd.try_spawn_to_json()?; Ok(result.data.data) }