1use std::collections::HashSet;
4use std::time::Duration;
5
6use aion_package::{ContentHash, ManifestDigest, ManifestVersion, Package};
7
8use crate::error::EngineError;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct LoadOutcome {
19 pub record: LoadedWorkflow,
21 pub freshly_loaded: bool,
23 pub route_changed: bool,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct LoadedWorkflow {
30 workflow_type: String,
31 deployed_entry_module: String,
32 entry_function: String,
33 version: ContentHash,
34 declared_timeout: Option<Duration>,
35}
36
37impl LoadedWorkflow {
38 pub(crate) const fn from_parts(
46 workflow_type: String,
47 deployed_entry_module: String,
48 entry_function: String,
49 version: ContentHash,
50 declared_timeout: Option<Duration>,
51 ) -> Self {
52 Self {
53 workflow_type,
54 deployed_entry_module,
55 entry_function,
56 version,
57 declared_timeout,
58 }
59 }
60
61 #[must_use]
63 pub fn workflow_type(&self) -> &str {
64 &self.workflow_type
65 }
66
67 #[must_use]
69 pub fn deployed_entry_module(&self) -> &str {
70 &self.deployed_entry_module
71 }
72
73 #[must_use]
75 pub fn entry_function(&self) -> &str {
76 &self.entry_function
77 }
78
79 #[must_use]
81 pub fn version(&self) -> &ContentHash {
82 &self.version
83 }
84
85 #[must_use]
91 pub fn declared_timeout(&self) -> Option<Duration> {
92 self.declared_timeout
93 }
94}
95
96pub(crate) struct StagedWorkflow {
98 pub(crate) workflow_type: String,
99 pub(crate) deployed_entry_module: String,
100 pub(crate) entry_function: String,
101 pub(crate) declared_timeout: Option<Duration>,
102}
103
104pub(crate) struct StagedLoad<'a> {
106 pub(crate) workflows: Vec<StagedWorkflow>,
107 pub(crate) manifest_version: ManifestVersion,
108 pub(crate) manifest_digest: ManifestDigest,
109 pub(crate) version: ContentHash,
110 pub(crate) modules: Vec<StagedModule<'a>>,
111}
112
113impl<'a> StagedLoad<'a> {
114 pub(crate) fn new(package: &'a Package) -> Result<Self, EngineError> {
115 let manifest = package.manifest();
116 let version = package.content_hash().clone();
117 let mut seen = HashSet::new();
125 let mut workflows = Vec::with_capacity(1 + manifest.additional_workflows.len());
126 let entries = std::iter::once((
127 manifest.entry_module.as_str(),
128 manifest.entry_module.as_str(),
129 manifest.entry_function.as_str(),
130 manifest.timeout,
131 ))
132 .chain(manifest.additional_workflows.iter().map(|entry| {
133 (
134 entry.workflow_type.as_str(),
135 entry.entry_module.as_str(),
136 entry.entry_function.as_str(),
137 entry.timeout,
138 )
139 }));
140 for (workflow_type, entry_module, entry_function, entry_timeout) in entries {
141 if !seen.insert(workflow_type) {
142 return Err(load_error(format!(
143 "package declares workflow type `{workflow_type}` more than once"
144 )));
145 }
146 if package.beams().get(entry_module).is_none() {
147 return Err(load_error(format!(
148 "manifest entry module `{entry_module}` for workflow `{workflow_type}` is absent from package beams"
149 )));
150 }
151 workflows.push(StagedWorkflow {
152 workflow_type: workflow_type.to_owned(),
153 deployed_entry_module: aion_package::deployed_name(entry_module, &version),
154 entry_function: entry_function.to_owned(),
155 declared_timeout: package.declared_entry_timeout(entry_timeout),
156 });
157 }
158 let modules = package
159 .deployed_modules()
160 .into_iter()
161 .map(|(deployed_name, bytes)| StagedModule {
162 deployed_name,
163 bytes,
164 })
165 .collect();
166
167 Ok(Self {
168 workflows,
169 manifest_version: manifest.version.clone(),
170 manifest_digest: manifest.canonical_digest()?,
171 version,
172 modules,
173 })
174 }
175
176 pub(crate) fn records(&self) -> Vec<LoadedWorkflow> {
178 self.workflows
179 .iter()
180 .map(|entry| {
181 LoadedWorkflow::from_parts(
182 entry.workflow_type.clone(),
183 entry.deployed_entry_module.clone(),
184 entry.entry_function.clone(),
185 self.version.clone(),
186 entry.declared_timeout,
187 )
188 })
189 .collect()
190 }
191}
192
193pub(crate) struct StagedModule<'a> {
195 pub(crate) deployed_name: String,
196 pub(crate) bytes: &'a [u8],
197}
198
199pub(crate) fn load_error(reason: String) -> EngineError {
200 EngineError::Load { reason }
201}
202
203pub(crate) fn rollback_registered<R>(rollback: &mut R, registered_now: &[String]) -> String
208where
209 R: FnMut(&str) -> Result<(), EngineError>,
210{
211 let mut errors = Vec::new();
212 for deployed_name in registered_now.iter().rev() {
213 if let Err(error) = rollback(deployed_name) {
214 errors.push(format!("{deployed_name}: {error}"));
215 }
216 }
217
218 if errors.is_empty() {
219 String::new()
220 } else {
221 format!("; rollback failed for {}", errors.join(", "))
222 }
223}