use crate::composition::Template;
use crate::config::{Config, Secret};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MissingPolicy {
Error,
Omit,
UseDefault,
Generate,
}
impl MissingPolicy {
pub(crate) fn guaranteed_on_success(self) -> bool {
self != Self::Omit
}
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledSecret {
pub(crate) config: Secret,
pub(crate) missing: MissingPolicy,
pub(crate) declared_required: bool,
pub(crate) composition: Option<Template>,
}
impl CompiledSecret {
fn new(config: Secret) -> Self {
let declared_required = config.required.unwrap_or(config.default.is_none());
let missing = if config.would_generate() {
MissingPolicy::Generate
} else if config.default.is_some() {
MissingPolicy::UseDefault
} else if declared_required {
MissingPolicy::Error
} else {
MissingPolicy::Omit
};
let composition = config
.composed
.as_deref()
.and_then(|source| Template::parse(source).ok());
Self {
config,
missing,
declared_required,
composition,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_policy_compiles_presence_once() {
let required = CompiledSecret::new(Secret::default());
assert_eq!(required.missing, MissingPolicy::Error);
assert!(required.declared_required);
assert!(required.missing.guaranteed_on_success());
let defaulted = CompiledSecret::new(Secret {
default: Some("fallback".to_string()),
..Default::default()
});
assert_eq!(defaulted.missing, MissingPolicy::UseDefault);
assert!(!defaulted.declared_required);
assert!(defaulted.missing.guaranteed_on_success());
let optional = CompiledSecret::new(Secret {
required: Some(false),
..Default::default()
});
assert_eq!(optional.missing, MissingPolicy::Omit);
assert!(!optional.missing.guaranteed_on_success());
}
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledProfile {
pub(crate) secrets: BTreeMap<String, CompiledSecret>,
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledManifest {
pub(crate) project: String,
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")
.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 secrets = 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())
.expect("an effective name comes from current or default");
(name.clone(), CompiledSecret::new(effective))
})
.collect();
profiles.insert(profile_name.clone(), CompiledProfile { secrets });
}
Self {
project: config.project.name.clone(),
profiles,
}
}
pub(crate) fn profile(&self, name: &str) -> Option<&CompiledProfile> {
self.profiles.get(name)
}
}