use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use systemprompt_manifest::secrets::Secrets;
use super::SecretsBootstrapError;
pub trait SecretsProvider {
fn fetch(&self) -> impl Future<Output = Result<SecretsDocument, SecretsBootstrapError>> + Send;
fn describe(&self) -> String;
}
pub type SecretsFetch<'a> =
Pin<Box<dyn Future<Output = Result<SecretsDocument, SecretsBootstrapError>> + Send + 'a>>;
pub trait DynSecretsProvider: Send + Sync {
fn fetch_boxed(&self) -> SecretsFetch<'_>;
fn describe(&self) -> String;
fn refresh_hint(&self) -> Option<Duration> {
None
}
}
impl<T: SecretsProvider + Send + Sync> DynSecretsProvider for T {
fn fetch_boxed(&self) -> SecretsFetch<'_> {
Box::pin(self.fetch())
}
fn describe(&self) -> String {
SecretsProvider::describe(self)
}
}
#[derive(Debug, Clone, Default)]
pub struct SecretsDocument(serde_json::Map<String, serde_json::Value>);
impl SecretsDocument {
#[must_use]
pub const fn new(fields: serde_json::Map<String, serde_json::Value>) -> Self {
Self(fields)
}
pub fn merge_field(&mut self, key: impl Into<String>, value: serde_json::Value) {
self.0.insert(key.into(), value);
}
#[must_use]
pub fn key_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.0.keys().cloned().collect();
names.sort_unstable();
names
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn into_secrets(self) -> Result<Secrets, SecretsBootstrapError> {
let body = serde_json::to_string(&serde_json::Value::Object(self.0))
.map_err(SecretsBootstrapError::DocumentEncode)?;
Secrets::parse(&body).map_err(SecretsBootstrapError::InvalidSecretsFile)
}
}