aion/workloop/
declared.rs1use std::collections::BTreeMap;
23use std::time::Duration;
24
25use aion_core::{CarryContract, InvariantSpec, ToleranceSpec, WorkloopArming, WorkloopSpec};
26use aion_package::{InvariantContract, ToleranceContract, WorkloopContract};
27
28use super::error::WorkloopError;
29
30pub fn spec_from_contract(
39 workflow_type: &str,
40 contract: &WorkloopContract,
41) -> Result<WorkloopSpec, WorkloopError> {
42 let arming = arming_from(workflow_type, contract)?;
43 let invariants = contract
44 .invariants
45 .iter()
46 .map(|invariant| invariant_from(workflow_type, invariant))
47 .collect::<Result<Vec<_>, _>>()?;
48 let retention = Duration::from_secs(contract.retention_seconds);
53 let carry = CarryContract::new(
54 contract
55 .carries
56 .iter()
57 .map(|carry| (carry.name.clone(), carry.default.clone()))
58 .collect::<BTreeMap<_, _>>(),
59 )
60 .map_err(|error| WorkloopError::Engine {
61 reason: format!(
62 "the deployed workloop declaration for `{workflow_type}` carries a seed the engine \
63 cannot write into generation 1's start payload: {error}"
64 ),
65 })?;
66 WorkloopSpec::with_carry(arming, invariants, retention, carry).map_err(|error| {
67 WorkloopError::Engine {
68 reason: format!(
69 "the deployed workloop declaration for `{workflow_type}` cannot be armed: \
70 {error}. Nothing was started and no registration was written"
71 ),
72 }
73 })
74}
75
76fn arming_from(
77 workflow_type: &str,
78 contract: &WorkloopContract,
79) -> Result<WorkloopArming, WorkloopError> {
80 let signals = contract.arms.clone();
81 match (contract.cadence_seconds, signals.is_empty()) {
82 (Some(seconds), true) => WorkloopArming::every(Duration::from_secs(seconds)),
83 (Some(seconds), false) => {
84 WorkloopArming::every_with_signals(Duration::from_secs(seconds), signals)
85 }
86 (None, false) => WorkloopArming::signal_only(signals),
87 (None, true) => {
88 return Err(WorkloopError::Engine {
89 reason: format!(
90 "the deployed workloop declaration for `{workflow_type}` arms on neither a \
91 cadence nor a signal, so nothing would ever wake it. The language refuses \
92 this (C5), so an archive carrying it was built by a toolchain that did not \
93 check it"
94 ),
95 });
96 }
97 }
98 .map_err(|error| WorkloopError::Engine {
99 reason: format!(
100 "the deployed workloop declaration for `{workflow_type}` cannot be armed: {error}"
101 ),
102 })
103}
104
105fn tolerance_refusal(workflow_type: &str, invariant: &str, reason: &str) -> WorkloopError {
107 WorkloopError::Engine {
108 reason: format!(
109 "invariant `{invariant}` of the deployed workloop `{workflow_type}` declares a \
110 tolerance the engine cannot act on: {reason}"
111 ),
112 }
113}
114
115fn invariant_from(
116 workflow_type: &str,
117 contract: &InvariantContract,
118) -> Result<InvariantSpec, WorkloopError> {
119 let mut windows = None;
120 let mut unconfirmed_for = None;
121 for tolerance in &contract.tolerances {
122 match tolerance {
123 ToleranceContract::Windows { count } => windows = Some(*count),
124 ToleranceContract::UnconfirmedFor { seconds } => {
125 unconfirmed_for = Some(Duration::from_secs(*seconds));
126 }
127 }
128 }
129 let tolerance = match (windows, unconfirmed_for) {
130 (Some(count), Some(duration)) => ToleranceSpec::both(count, duration).map_err(|error| {
131 tolerance_refusal(workflow_type, &contract.name, &error.to_string())
132 })?,
133 (Some(count), None) => ToleranceSpec::count(count),
134 (None, Some(duration)) => ToleranceSpec::duration(duration).map_err(|error| {
135 tolerance_refusal(workflow_type, &contract.name, &error.to_string())
136 })?,
137 (None, None) => {
140 return Err(WorkloopError::Engine {
141 reason: format!(
142 "invariant `{}` of the deployed workloop `{workflow_type}` declares no \
143 tolerance, and tolerance has no default (R2.3) — the engine cannot invent \
144 the threshold at which it alarms",
145 contract.name
146 ),
147 });
148 }
149 };
150 let confirms = contract
155 .confirms
156 .clone()
157 .ok_or_else(|| WorkloopError::Engine {
158 reason: format!(
159 "invariant `{}` of the deployed workloop `{workflow_type}` names no confirming \
160 route, so nothing in the document could ever confirm it and it would alarm at \
161 every window forever",
162 contract.name
163 ),
164 })?;
165 Ok(InvariantSpec {
166 name: contract.name.clone(),
167 record_type: contract.record_type.clone(),
168 tolerance,
169 confirms: vec![confirms],
170 })
171}