use std::collections::HashSet;
use aion_package::{ContentHash, ManifestDigest, ManifestVersion, Package};
use crate::error::EngineError;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LoadOutcome {
pub record: LoadedWorkflow,
pub freshly_loaded: bool,
pub route_changed: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoadedWorkflow {
workflow_type: String,
deployed_entry_module: String,
entry_function: String,
version: ContentHash,
}
impl LoadedWorkflow {
pub(crate) const fn from_parts(
workflow_type: String,
deployed_entry_module: String,
entry_function: String,
version: ContentHash,
) -> Self {
Self {
workflow_type,
deployed_entry_module,
entry_function,
version,
}
}
#[must_use]
pub fn workflow_type(&self) -> &str {
&self.workflow_type
}
#[must_use]
pub fn deployed_entry_module(&self) -> &str {
&self.deployed_entry_module
}
#[must_use]
pub fn entry_function(&self) -> &str {
&self.entry_function
}
#[must_use]
pub fn version(&self) -> &ContentHash {
&self.version
}
}
pub(crate) struct StagedWorkflow {
pub(crate) workflow_type: String,
pub(crate) deployed_entry_module: String,
pub(crate) entry_function: String,
}
pub(crate) struct StagedLoad<'a> {
pub(crate) workflows: Vec<StagedWorkflow>,
pub(crate) manifest_version: ManifestVersion,
pub(crate) manifest_digest: ManifestDigest,
pub(crate) version: ContentHash,
pub(crate) modules: Vec<StagedModule<'a>>,
}
impl<'a> StagedLoad<'a> {
pub(crate) fn new(package: &'a Package) -> Result<Self, EngineError> {
let manifest = package.manifest();
let version = package.content_hash().clone();
let mut seen = HashSet::new();
let mut workflows = Vec::with_capacity(1 + manifest.additional_workflows.len());
let entries = std::iter::once((
manifest.entry_module.as_str(),
manifest.entry_module.as_str(),
manifest.entry_function.as_str(),
))
.chain(manifest.additional_workflows.iter().map(|entry| {
(
entry.workflow_type.as_str(),
entry.entry_module.as_str(),
entry.entry_function.as_str(),
)
}));
for (workflow_type, entry_module, entry_function) in entries {
if !seen.insert(workflow_type) {
return Err(load_error(format!(
"package declares workflow type `{workflow_type}` more than once"
)));
}
if package.beams().get(entry_module).is_none() {
return Err(load_error(format!(
"manifest entry module `{entry_module}` for workflow `{workflow_type}` is absent from package beams"
)));
}
workflows.push(StagedWorkflow {
workflow_type: workflow_type.to_owned(),
deployed_entry_module: aion_package::deployed_name(entry_module, &version),
entry_function: entry_function.to_owned(),
});
}
let modules = package
.deployed_modules()
.into_iter()
.map(|(deployed_name, bytes)| StagedModule {
deployed_name,
bytes,
})
.collect();
Ok(Self {
workflows,
manifest_version: manifest.version.clone(),
manifest_digest: manifest.canonical_digest()?,
version,
modules,
})
}
pub(crate) fn records(&self) -> Vec<LoadedWorkflow> {
self.workflows
.iter()
.map(|entry| {
LoadedWorkflow::from_parts(
entry.workflow_type.clone(),
entry.deployed_entry_module.clone(),
entry.entry_function.clone(),
self.version.clone(),
)
})
.collect()
}
}
pub(crate) struct StagedModule<'a> {
pub(crate) deployed_name: String,
pub(crate) bytes: &'a [u8],
}
pub(crate) fn load_error(reason: String) -> EngineError {
EngineError::Load { reason }
}
pub(crate) fn rollback_registered<R>(rollback: &mut R, registered_now: &[String]) -> String
where
R: FnMut(&str) -> Result<(), EngineError>,
{
let mut errors = Vec::new();
for deployed_name in registered_now.iter().rev() {
if let Err(error) = rollback(deployed_name) {
errors.push(format!("{deployed_name}: {error}"));
}
}
if errors.is_empty() {
String::new()
} else {
format!("; rollback failed for {}", errors.join(", "))
}
}