1use std::collections::HashSet;
4
5use aion_package::{ContentHash, ManifestDigest, ManifestVersion, Package};
6
7use crate::error::EngineError;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct LoadOutcome {
18 pub record: LoadedWorkflow,
20 pub freshly_loaded: bool,
22 pub route_changed: bool,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct LoadedWorkflow {
29 workflow_type: String,
30 deployed_entry_module: String,
31 entry_function: String,
32 version: ContentHash,
33}
34
35impl LoadedWorkflow {
36 pub(crate) const fn from_parts(
38 workflow_type: String,
39 deployed_entry_module: String,
40 entry_function: String,
41 version: ContentHash,
42 ) -> Self {
43 Self {
44 workflow_type,
45 deployed_entry_module,
46 entry_function,
47 version,
48 }
49 }
50
51 #[must_use]
53 pub fn workflow_type(&self) -> &str {
54 &self.workflow_type
55 }
56
57 #[must_use]
59 pub fn deployed_entry_module(&self) -> &str {
60 &self.deployed_entry_module
61 }
62
63 #[must_use]
65 pub fn entry_function(&self) -> &str {
66 &self.entry_function
67 }
68
69 #[must_use]
71 pub fn version(&self) -> &ContentHash {
72 &self.version
73 }
74}
75
76pub(crate) struct StagedWorkflow {
78 pub(crate) workflow_type: String,
79 pub(crate) deployed_entry_module: String,
80 pub(crate) entry_function: String,
81}
82
83pub(crate) struct StagedLoad<'a> {
85 pub(crate) workflows: Vec<StagedWorkflow>,
86 pub(crate) manifest_version: ManifestVersion,
87 pub(crate) manifest_digest: ManifestDigest,
88 pub(crate) version: ContentHash,
89 pub(crate) modules: Vec<StagedModule<'a>>,
90}
91
92impl<'a> StagedLoad<'a> {
93 pub(crate) fn new(package: &'a Package) -> Result<Self, EngineError> {
94 let manifest = package.manifest();
95 let version = package.content_hash().clone();
96 let mut seen = HashSet::new();
97 let mut workflows = Vec::with_capacity(1 + manifest.additional_workflows.len());
98 let entries = std::iter::once((
99 manifest.entry_module.as_str(),
100 manifest.entry_module.as_str(),
101 manifest.entry_function.as_str(),
102 ))
103 .chain(manifest.additional_workflows.iter().map(|entry| {
104 (
105 entry.workflow_type.as_str(),
106 entry.entry_module.as_str(),
107 entry.entry_function.as_str(),
108 )
109 }));
110 for (workflow_type, entry_module, entry_function) in entries {
111 if !seen.insert(workflow_type) {
112 return Err(load_error(format!(
113 "package declares workflow type `{workflow_type}` more than once"
114 )));
115 }
116 if package.beams().get(entry_module).is_none() {
117 return Err(load_error(format!(
118 "manifest entry module `{entry_module}` for workflow `{workflow_type}` is absent from package beams"
119 )));
120 }
121 workflows.push(StagedWorkflow {
122 workflow_type: workflow_type.to_owned(),
123 deployed_entry_module: aion_package::deployed_name(entry_module, &version),
124 entry_function: entry_function.to_owned(),
125 });
126 }
127 let modules = package
128 .deployed_modules()
129 .into_iter()
130 .map(|(deployed_name, bytes)| StagedModule {
131 deployed_name,
132 bytes,
133 })
134 .collect();
135
136 Ok(Self {
137 workflows,
138 manifest_version: manifest.version.clone(),
139 manifest_digest: manifest.canonical_digest()?,
140 version,
141 modules,
142 })
143 }
144
145 pub(crate) fn records(&self) -> Vec<LoadedWorkflow> {
147 self.workflows
148 .iter()
149 .map(|entry| {
150 LoadedWorkflow::from_parts(
151 entry.workflow_type.clone(),
152 entry.deployed_entry_module.clone(),
153 entry.entry_function.clone(),
154 self.version.clone(),
155 )
156 })
157 .collect()
158 }
159}
160
161pub(crate) struct StagedModule<'a> {
163 pub(crate) deployed_name: String,
164 pub(crate) bytes: &'a [u8],
165}
166
167pub(crate) fn load_error(reason: String) -> EngineError {
168 EngineError::Load { reason }
169}
170
171pub(crate) fn rollback_registered<R>(rollback: &mut R, registered_now: &[String]) -> String
176where
177 R: FnMut(&str) -> Result<(), EngineError>,
178{
179 let mut errors = Vec::new();
180 for deployed_name in registered_now.iter().rev() {
181 if let Err(error) = rollback(deployed_name) {
182 errors.push(format!("{deployed_name}: {error}"));
183 }
184 }
185
186 if errors.is_empty() {
187 String::new()
188 } else {
189 format!("; rollback failed for {}", errors.join(", "))
190 }
191}