use serde_json::{json, Value};
use crate::api::errors::{exit_code_for, id_str, ErrorId};
use crate::logging::StageLogger;
use super::perf::PerfAgg;
pub(crate) struct ApplySummary {
fields: Value,
}
impl ApplySummary {
pub(crate) fn new(lock_backend: &str, lock_wait_ms: Option<u64>) -> Self {
let fields = json!({
"lock_backend": lock_backend,
"lock_wait_ms": lock_wait_ms,
});
Self { fields }
}
pub(crate) fn perf(mut self, total: PerfAgg) -> Self {
if let Some(obj) = self.fields.as_object_mut() {
obj.insert(
"perf".to_string(),
json!({
"hash_ms": total.hash,
"backup_ms": total.backup,
"swap_ms": total.swap,
}),
);
obj.insert("fsync_ms".to_string(), json!(total.swap));
}
self
}
pub(crate) fn rolled_back_paths(mut self, paths: &[String]) -> Self {
if let Some(obj) = self.fields.as_object_mut() {
obj.insert("rolled_back".to_string(), json!(true));
obj.insert("rolled_back_paths".to_string(), json!(paths));
}
self
}
pub(crate) fn no_rollback(mut self) -> Self {
if let Some(obj) = self.fields.as_object_mut() {
obj.insert("rolled_back".to_string(), json!(false));
obj.insert("rolled_back_paths".to_string(), json!([]));
}
self
}
pub(crate) fn executed_counts(
mut self,
executed_count: usize,
rolled_back_count: usize,
) -> Self {
if let Some(obj) = self.fields.as_object_mut() {
obj.insert("executed_count".to_string(), json!(executed_count));
obj.insert("rolled_back_count".to_string(), json!(rolled_back_count));
}
self
}
pub(crate) fn errors(mut self, errors: &[String]) -> Self {
if let Some(obj) = self.fields.as_object_mut() {
let chain = crate::api::errors::infer_summary_error_ids(errors);
obj.insert("summary_error_ids".to_string(), json!(chain));
}
self
}
pub(crate) fn smoke_or_policy_mapping(mut self, errors: &[String]) -> Self {
if errors.is_empty() {
return self;
}
if let Some(obj) = self.fields.as_object_mut() {
let chain = crate::api::errors::infer_summary_error_ids(errors);
let has = |s: &str| chain.contains(&s);
let pick = if has(id_str(ErrorId::E_SMOKE)) {
ErrorId::E_SMOKE
} else if has(id_str(ErrorId::E_EXDEV)) {
ErrorId::E_EXDEV
} else if has(id_str(ErrorId::E_LOCKING)) {
ErrorId::E_LOCKING
} else if has(id_str(ErrorId::E_BACKUP_MISSING)) {
ErrorId::E_BACKUP_MISSING
} else if has(id_str(ErrorId::E_RESTORE_FAILED)) {
ErrorId::E_RESTORE_FAILED
} else if has(id_str(ErrorId::E_ATOMIC_SWAP)) {
ErrorId::E_ATOMIC_SWAP
} else {
ErrorId::E_POLICY
};
obj.insert("error_id".to_string(), json!(id_str(pick)));
obj.insert("exit_code".to_string(), json!(exit_code_for(pick)));
}
self
}
pub(crate) fn attestation<E: crate::logging::FactsEmitter, A: crate::logging::AuditSink>(
mut self,
api: &crate::api::Switchyard<E, A>,
pid: uuid::Uuid,
executed_len: usize,
rolled_back: bool,
) -> Self {
if let Some(att) = &api.attest {
let bundle_json = json!({
"plan_id": pid.to_string(),
"executed": executed_len,
"rolled_back": rolled_back,
});
let bundle: Vec<u8> = serde_json::to_vec(&bundle_json).unwrap_or_default();
if let Some(att_json) =
crate::adapters::attest::build_attestation_fields(&**att, &bundle)
{
if let Some(obj) = self.fields.as_object_mut() {
obj.insert("attestation".to_string(), att_json);
}
}
}
self
}
pub(crate) fn emit(self, slog: &StageLogger<'_>, decision: &str) {
match decision {
"failure" => slog.apply_result().merge(&self.fields).emit_failure(),
_ => slog.apply_result().merge(&self.fields).emit_success(),
}
}
}