1use aion::DeployedWorkerContract;
24use aion_package::{ActionBodyContract, ContentHash};
25
26use super::declared_body::DeclaredBodyLookup;
27use super::declared_body_ambiguity::DeclaringVersion;
28
29#[must_use]
38pub fn select_declared_body(
39 contracts: &[DeployedWorkerContract],
40 action: &str,
41 run_version: Option<&ContentHash>,
42) -> DeclaredBodyLookup {
43 match run_version {
44 Some(version) => body_of_version(contracts, action, version),
45 None => body_across_queue(contracts, action),
46 }
47}
48
49fn body_of_version(
53 contracts: &[DeployedWorkerContract],
54 action: &str,
55 version: &ContentHash,
56) -> DeclaredBodyLookup {
57 for deployed in contracts {
58 if &deployed.package_version != version {
59 continue;
60 }
61 for declared in &deployed.contract.actions {
62 if declared.name == action
63 && let Some(body) = &declared.body
64 {
65 return DeclaredBodyLookup::Declared(body.clone());
66 }
67 }
68 }
69 DeclaredBodyLookup::None
70}
71
72fn body_across_queue(contracts: &[DeployedWorkerContract], action: &str) -> DeclaredBodyLookup {
77 let mut bodies: Vec<ActionBodyContract> = Vec::new();
78 let mut declaring: Vec<DeclaringVersion> = Vec::new();
79 for deployed in contracts {
80 for declared in &deployed.contract.actions {
81 if declared.name == action
82 && let Some(body) = &declared.body
83 {
84 let index = bodies
85 .iter()
86 .position(|seen| seen == body)
87 .unwrap_or_else(|| {
88 bodies.push(body.clone());
89 bodies.len() - 1
90 });
91 declaring.push(DeclaringVersion {
92 content_hash: deployed.package_version.to_string(),
93 workflow_types: deployed.workflow_types.clone(),
94 route_active: deployed.route_active,
95 body: index,
96 });
97 break;
101 }
102 }
103 }
104 match bodies.len() {
105 0 => DeclaredBodyLookup::None,
106 1 => match bodies.pop() {
107 Some(body) => DeclaredBodyLookup::Declared(body),
108 None => DeclaredBodyLookup::None,
111 },
112 _ => DeclaredBodyLookup::Ambiguous { declaring },
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use aion::DeployedWorkerContract;
119 use aion_package::{ActionBodyContract, ActionContract, ContentHash, WorkerContract};
120
121 use super::super::declared_body::DeclaredBodyLookup;
122 use super::select_declared_body;
123
124 fn version(byte: u8) -> ContentHash {
125 ContentHash::from_bytes([byte; 32])
126 }
127
128 fn contract(
131 hash: u8,
132 workflow_type: &str,
133 route_active: bool,
134 action: &str,
135 command: Option<&str>,
136 ) -> DeployedWorkerContract {
137 DeployedWorkerContract {
138 package_version: version(hash),
139 contract: WorkerContract {
140 task_queue: "local".to_owned(),
141 actions: vec![ActionContract {
142 name: action.to_owned(),
143 input_schema: serde_json::json!({"type": "object"}),
144 output_schema: serde_json::json!({"type": "object"}),
145 node: None,
146 timeout: None,
147 retry: None,
148 advisory: false,
149 agent: false,
150 body: command.map(|command| ActionBodyContract::Run {
151 command: command.to_owned(),
152 }),
153 }],
154 },
155 workflow_types: vec![workflow_type.to_owned()],
156 route_active,
157 }
158 }
159
160 fn command_of(lookup: &DeclaredBodyLookup) -> Option<&str> {
161 match lookup {
162 DeclaredBodyLookup::Declared(ActionBodyContract::Run { command }) => Some(command),
163 _ => None,
164 }
165 }
166
167 #[test]
171 fn an_in_flight_run_keeps_its_own_versions_body_after_a_redeploy() {
172 let retained = [
173 contract(0x0a, "sweep", false, "collect", Some("echo old")),
174 contract(0x0b, "sweep", true, "collect", Some("echo new")),
175 ];
176
177 let old_run = select_declared_body(&retained, "collect", Some(&version(0x0a)));
178 assert_eq!(
179 command_of(&old_run),
180 Some("echo old"),
181 "the run pinned to the superseded version must keep its own body: {old_run:?}"
182 );
183
184 let new_run = select_declared_body(&retained, "collect", Some(&version(0x0b)));
185 assert_eq!(
186 command_of(&new_run),
187 Some("echo new"),
188 "a run on the route-active version gets the new body: {new_run:?}"
189 );
190
191 let blind = select_declared_body(&retained, "collect", None);
195 assert!(
196 matches!(blind, DeclaredBodyLookup::Ambiguous { .. }),
197 "without the run's version the queue-wide reading must still refuse: {blind:?}"
198 );
199 }
200
201 #[test]
205 fn a_version_without_a_body_delegates_rather_than_borrowing_one() {
206 let retained = [
207 contract(0x0a, "sweep", false, "collect", None),
208 contract(0x0b, "sweep", true, "collect", Some("echo new")),
209 ];
210 let lookup = select_declared_body(&retained, "collect", Some(&version(0x0a)));
211 assert!(
212 matches!(lookup, DeclaredBodyLookup::None),
213 "a run whose package declares no body must delegate, not borrow: {lookup:?}"
214 );
215 }
216
217 #[test]
220 fn a_version_absent_from_the_queue_declares_nothing_here() {
221 let retained = [contract(0x0b, "sweep", true, "collect", Some("echo new"))];
222 let lookup = select_declared_body(&retained, "collect", Some(&version(0x0c)));
223 assert!(
224 matches!(lookup, DeclaredBodyLookup::None),
225 "an unrelated version cannot pick up this queue's body: {lookup:?}"
226 );
227 }
228
229 #[test]
232 fn identical_bodies_across_versions_still_collapse_to_one() {
233 let retained = [
234 contract(0x0a, "sweep", false, "collect", Some("echo same")),
235 contract(0x0b, "sweep", true, "collect", Some("echo same")),
236 ];
237 let lookup = select_declared_body(&retained, "collect", None);
238 assert_eq!(command_of(&lookup), Some("echo same"), "{lookup:?}");
239 }
240
241 #[test]
243 fn a_bodiless_action_is_none_with_or_without_the_run_version() {
244 let retained = [contract(0x0a, "sweep", true, "collect", None)];
245 assert!(matches!(
246 select_declared_body(&retained, "collect", None),
247 DeclaredBodyLookup::None
248 ));
249 assert!(matches!(
250 select_declared_body(&retained, "collect", Some(&version(0x0a))),
251 DeclaredBodyLookup::None
252 ));
253 }
254
255 #[test]
258 fn the_blind_refusal_still_names_every_declaring_version() -> Result<(), String> {
259 let retained = [
260 contract(0x0a, "sweep", false, "collect", Some("echo old")),
261 contract(0x0b, "sweep", true, "collect", Some("echo new")),
262 ];
263 let lookup = select_declared_body(&retained, "collect", None);
264 let DeclaredBodyLookup::Ambiguous { declaring } = lookup else {
265 return Err(format!(
266 "different bodies must refuse when read blind: {lookup:?}"
267 ));
268 };
269 assert_eq!(declaring.len(), 2, "{declaring:?}");
270 assert_eq!(declaring[0].content_hash, version(0x0a).to_string());
271 assert!(!declaring[0].route_active);
272 assert!(declaring[1].route_active);
273 assert_ne!(
274 declaring[0].body, declaring[1].body,
275 "the two versions must be recorded as carrying DIFFERENT bodies"
276 );
277 Ok(())
278 }
279}