use std::collections::BTreeMap;
use aion::WorkflowVersionInfo;
use aion_package::{ExtractionLimits, Package};
use aion_store::PackageRecord;
use super::types::{DeployedSourceState, DeployedVersion};
pub(super) fn project_versions(
catalog: Vec<WorkflowVersionInfo>,
archives: &[PackageRecord],
) -> Vec<DeployedVersion> {
let mut by_hash: BTreeMap<&str, &PackageRecord> = BTreeMap::new();
for record in archives {
by_hash
.entry(record.content_hash.as_str())
.or_insert(record);
}
let mut merged: BTreeMap<(String, String), DeployedVersion> = BTreeMap::new();
for info in catalog {
let content_hash = info.content_hash.to_string();
merged.insert(
(info.workflow_type.clone(), content_hash.clone()),
DeployedVersion {
workflow_type: info.workflow_type,
content_hash,
loaded: true,
route_active: info.route_active,
loaded_at: Some(info.loaded_at.to_rfc3339()),
deployed_at: None,
source: DeployedSourceState::NotPersisted,
},
);
}
for record in archives {
let key = (record.workflow_type.clone(), record.content_hash.clone());
merged.entry(key).or_insert_with(|| DeployedVersion {
workflow_type: record.workflow_type.clone(),
content_hash: record.content_hash.clone(),
loaded: false,
route_active: false,
loaded_at: None,
deployed_at: None,
source: DeployedSourceState::NotPersisted,
});
}
let mut evaluated: BTreeMap<String, DeployedSourceState> = BTreeMap::new();
let mut versions: Vec<DeployedVersion> = merged.into_values().collect();
for version in &mut versions {
let Some(record) = by_hash.get(version.content_hash.as_str()).copied() else {
continue;
};
version.deployed_at = Some(record.deployed_at.to_rfc3339());
version.source = evaluated
.entry(record.content_hash.clone())
.or_insert_with(|| evaluate_archive(record))
.clone();
}
versions.sort_by(|left, right| {
left.workflow_type
.cmp(&right.workflow_type)
.then_with(|| newest(right).cmp(&newest(left)))
.then_with(|| left.content_hash.cmp(&right.content_hash))
});
versions
}
fn newest(version: &DeployedVersion) -> Option<&str> {
match (version.deployed_at.as_deref(), version.loaded_at.as_deref()) {
(Some(deployed), Some(loaded)) => Some(deployed.max(loaded)),
(Some(only), None) | (None, Some(only)) => Some(only),
(None, None) => None,
}
}
fn evaluate_archive(record: &PackageRecord) -> DeployedSourceState {
match Package::load_from_bytes(&record.archive, ExtractionLimits::unbounded()) {
Ok(package) => package.awl().map_or(DeployedSourceState::Absent, |awl| {
DeployedSourceState::Available {
document_name: awl.document_name().to_owned(),
schema_count: awl.schemas().len(),
}
}),
Err(error) => DeployedSourceState::Unreadable {
reason: error.to_string(),
},
}
}