use crate::digest::Digest;
use super::readiness::Requirement;
use super::{ConfigurationState, Plan, RecordState};
#[must_use]
pub fn canonical(plan: &Plan) -> String {
let mut lines: Vec<String> = Vec::new();
lines.push(format!("target\t{}", plan.observed_state.repository.target));
lines.push(format!(
"candidate\t{}\t{}",
plan.release.candidate.payload_sha256, plan.release.candidate.payload_schema
));
lines.push(format!(
"record\t{}",
match &plan.observed_state.installation.record {
RecordState::Absent => "absent".to_owned(),
RecordState::Present { sha256, .. } => sha256.to_string(),
RecordState::Invalid { reason } => format!("invalid\t{reason}"),
}
));
lines.push(format!(
"configuration\t{}",
match &plan.observed_state.installation.configuration {
ConfigurationState {
sha256: Some(digest),
..
} => digest.to_string(),
ConfigurationState {
invalid: Some(reason),
..
} => format!("invalid\t{reason}"),
ConfigurationState { .. } => "absent".to_owned(),
}
));
lines.extend(
plan.operations
.iter()
.map(|operation| format!("operation\t{}", operation.canonical())),
);
let mut required: Vec<String> = plan
.preconditions
.iter()
.filter(|precondition| precondition.requirement == Requirement::Required)
.map(|precondition| {
format!(
"precondition\t{}\t{}",
precondition.id,
precondition.evaluation.word()
)
})
.collect();
required.sort();
lines.extend(required);
let mut selected: Vec<String> = plan
.decisions
.iter()
.filter_map(|decision| {
decision
.selected
.as_ref()
.map(|answer| format!("decision\t{}\t{answer}", decision.id))
})
.collect();
selected.sort();
lines.extend(selected);
let mut text = lines.join("\n");
text.push('\n');
text
}
#[must_use]
pub fn compute(plan: &Plan) -> Digest {
Digest::of(canonical(plan).as_bytes())
}
#[must_use]
pub fn plan_id(fingerprint: &Digest, created_at: &str) -> String {
let digest = Digest::of(format!("{fingerprint}\t{created_at}").as_bytes());
digest.to_string().chars().take(16).collect()
}