use std::collections::BTreeMap;
use std::collections::BTreeSet;
use serde::Deserialize;
use serde::Serialize;
use crate::config::Config;
use crate::config::Secret;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Manifest {
pub project: ManifestProject,
pub profiles: BTreeMap<String, ManifestProfile>,
pub groups: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManifestProject {
pub name: String,
pub revision: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManifestProfile {
pub secrets: BTreeMap<String, ManifestSecret>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManifestSecret {
pub required: bool,
pub has_default: bool,
pub as_path: bool,
pub groups: Vec<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledSecret {
pub(crate) config: Secret,
pub(crate) declared_required: bool,
}
impl CompiledSecret {
fn new(config: Secret, conditionally_required: bool) -> Self {
let declared_required = config
.required
.unwrap_or(config.default.is_none() && !conditionally_required);
Self {
config,
declared_required,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledProfile {
pub(crate) secrets: BTreeMap<String, CompiledSecret>,
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledManifest {
pub(crate) profiles: BTreeMap<String, CompiledProfile>,
}
impl CompiledManifest {
pub(crate) fn compile(config: &Config) -> Self {
let default_profile = config.profiles.get("default");
let mut profiles = BTreeMap::new();
for (profile_name, profile) in &config.profiles {
let inherited = (profile_name != "default" && profile.inherits_default())
.then_some(default_profile)
.flatten();
let mut names: BTreeSet<&String> = profile.secrets.keys().collect();
if let Some(default) = inherited {
names.extend(default.secrets.keys());
}
let effective: BTreeMap<String, Secret> = names
.into_iter()
.map(|name| {
let current = profile.secrets.get(name);
let default = inherited.and_then(|p| p.secrets.get(name));
let effective = Secret::resolved(
current,
default,
profile.defaults.as_ref(),
config.defaults.as_ref(),
)
.expect("an effective name comes from current or default");
(name.clone(), effective)
})
.collect();
let secrets = effective
.into_iter()
.map(|(name, secret)| {
let conditionally_required = secret
.at_least_one
.as_ref()
.is_some_and(|groups| !groups.is_empty())
|| secret
.exactly_one
.as_ref()
.is_some_and(|groups| !groups.is_empty());
(name, CompiledSecret::new(secret, conditionally_required))
})
.collect();
profiles.insert(profile_name.clone(), CompiledProfile { secrets });
}
Self { profiles }
}
pub(crate) fn public_manifest(&self, config: &Config) -> Manifest {
let profiles = self
.profiles
.iter()
.map(|(name, profile)| {
let secrets = profile
.secrets
.iter()
.map(|(name, secret)| {
(
name.clone(),
ManifestSecret {
required: secret.declared_required,
has_default: secret.config.default.is_some(),
as_path: secret.config.as_path.unwrap_or(false),
groups: secret.config.groups.clone().unwrap_or_default(),
},
)
})
.collect();
(name.clone(), ManifestProfile { secrets })
})
.collect();
Manifest {
project: ManifestProject {
name: config.project.name.clone(),
revision: config.project.revision.clone(),
},
profiles,
groups: config
.groups
.clone()
.unwrap_or_default()
.into_iter()
.collect(),
}
}
}