pub struct CanwuError {
pub code: ErrorCode,
pub message: String,
pub related_entities: Vec<EntityRef>,
}Fields§
§code: ErrorCode§message: StringImplementations§
Source§impl CanwuError
impl CanwuError
Sourcepub fn new(code: ErrorCode, message: impl Into<String>) -> CanwuError
pub fn new(code: ErrorCode, message: impl Into<String>) -> CanwuError
Examples found in repository?
examples/governance_transition.rs (lines 141-144)
139fn action_hash(grain_units: u64) -> Result<String, CanwuError> {
140 let payload = serde_json::to_value(action_payload(grain_units)).map_err(|error| {
141 CanwuError::new(
142 ErrorCode::InvalidPayload,
143 format!("relief action payload cannot be encoded: {error}"),
144 )
145 })?;
146 canonical_hash(ACTION_HASH_DOMAIN, &payload)
147}
148
149fn issue_descriptor() -> PluginIngressDescriptor {
150 PluginIngressDescriptor {
151 name: ISSUE_INGRESS.to_owned(),
152 description: "Issue a central relief order".to_owned(),
153 class: IngressClass::ScheduledSystem,
154 payload_schema: object_schema(&[("order_id", PayloadValueType::String)]),
155 }
156}
157
158fn execute_descriptor() -> PluginIngressDescriptor {
159 PluginIngressDescriptor {
160 name: EXECUTE_INGRESS.to_owned(),
161 description: "Admit an owner execution of a relief order".to_owned(),
162 class: IngressClass::ScheduledSystem,
163 payload_schema: object_schema(&[("order_id", PayloadValueType::String)]),
164 }
165}
166
167fn owned_order_ingress(
168 view: &SimulationView<'_>,
169 context: &BoundaryContext,
170 owner: &str,
171 packet_type: &str,
172) -> Result<Option<String>, CanwuError> {
173 let mut order_id = None;
174 for ingress_id in &context.admitted_ingress {
175 let Some(record) = view.ingress(*ingress_id)? else {
176 continue;
177 };
178 let IngressPayload::Plugin {
179 plugin,
180 packet_type: admitted_packet_type,
181 payload,
182 ..
183 } = &record.payload
184 else {
185 continue;
186 };
187 if plugin != owner || admitted_packet_type != packet_type {
188 continue;
189 }
190 if order_id.is_some() {
191 return Err(CanwuError::new(
192 ErrorCode::InvalidBoundary,
193 format!("{owner} received duplicate {packet_type} ingress"),
194 ));
195 }
196 let value = payload
197 .get("order_id")
198 .and_then(Value::as_str)
199 .ok_or_else(|| {
200 CanwuError::new(
201 ErrorCode::InvalidPayload,
202 format!("{owner}.{packet_type} is missing order_id"),
203 )
204 })?;
205 order_id = Some(value.to_owned());
206 }
207 Ok(order_id)
208}
209
210fn order_payload(view: &SimulationView<'_>) -> Result<ReliefOrder, CanwuError> {
211 view.typed_domain_record(&order_reference())?
212 .ok_or_else(|| CanwuError::new(ErrorCode::InvalidBoundary, "relief order is missing"))?
213 .decode_payload::<ReliefOrderRecord>()
214}
215
216fn validate_order(
217 order: &ReliefOrder,
218 expected_system: &str,
219 expected_plugin: &str,
220 expected_hash: &str,
221) -> Result<(), CanwuError> {
222 let valid = order.order_id == ORDER_ID
223 && order.issued_by == CENTRAL_PLUGIN
224 && order.treasury_system == "prepare-treasury"
225 && order.treasury_version == 1
226 && order.treasury_disposition == "committed"
227 && order.county_system == "prepare-county"
228 && order.county_version == 1
229 && order.county_disposition == "committed"
230 && ((expected_plugin == TREASURY_PLUGIN
231 && expected_system == "prepare-treasury"
232 && order.treasury_hash == expected_hash)
233 || (expected_plugin == COUNTY_PLUGIN
234 && expected_system == "prepare-county"
235 && order.county_hash == expected_hash));
236 if !valid {
237 return Err(CanwuError::new(
238 ErrorCode::InvalidBoundary,
239 format!("{expected_plugin} received an invalid relief manifest"),
240 ));
241 }
242 Ok(())
243}
244
245fn publish_order(
246 view: &SimulationView<'_>,
247 context: &BoundaryContext,
248) -> Result<BoundaryProposal, CanwuError> {
249 let Some(order_id) = owned_order_ingress(view, context, CENTRAL_PLUGIN, ISSUE_INGRESS)? else {
250 return Ok(BoundaryProposal::default());
251 };
252 if order_id != ORDER_ID {
253 return Err(CanwuError::new(
254 ErrorCode::InvalidBoundary,
255 "the issue ingress names an unexpected relief order",
256 ));
257 }
258 if view.typed_domain_record(&order_reference())?.is_some() {
259 return Err(CanwuError::new(
260 ErrorCode::InvalidBoundary,
261 "the relief order already exists; duplicate issue ingress is invalid",
262 ));
263 }
264 let treasury_hash = action_hash(600)?;
265 let county_hash = action_hash(600)?;
266 let manifest = DomainRecordDraft::from_typed(
267 order_reference(),
268 &ReliefOrder {
269 order_id: ORDER_ID.to_owned(),
270 issued_by: CENTRAL_PLUGIN.to_owned(),
271 treasury_system: "prepare-treasury".to_owned(),
272 treasury_version: 1,
273 treasury_disposition: "committed".to_owned(),
274 treasury_hash,
275 county_system: "prepare-county".to_owned(),
276 county_version: 1,
277 county_disposition: "committed".to_owned(),
278 county_hash,
279 },
280 )?;
281 Ok(BoundaryProposal {
282 directives: vec![
283 BoundaryDirective::MutateRecord {
284 mutation: DomainRecordMutation::Create { record: manifest },
285 summary: "Publish the central relief order manifest".to_owned(),
286 },
287 BoundaryDirective::SchedulePluginIngress {
288 target_plugin: TREASURY_PLUGIN.to_owned(),
289 after: SimDuration::ZERO,
290 packet_type: EXECUTE_INGRESS.to_owned(),
291 priority: 0,
292 payload: json!({"order_id": ORDER_ID}),
293 affected: Vec::new(),
294 },
295 BoundaryDirective::SchedulePluginIngress {
296 target_plugin: COUNTY_PLUGIN.to_owned(),
297 after: SimDuration::ZERO,
298 packet_type: EXECUTE_INGRESS.to_owned(),
299 priority: 0,
300 payload: json!({"order_id": ORDER_ID}),
301 affected: Vec::new(),
302 },
303 ],
304 ..BoundaryProposal::default()
305 })
306}
307
308fn prepare_treasury(
309 view: &SimulationView<'_>,
310 context: &BoundaryContext,
311) -> Result<BoundaryProposal, CanwuError> {
312 prepare_owner(
313 view,
314 context,
315 TREASURY_PLUGIN,
316 "prepare-treasury",
317 treasury_reference(),
318 &action_payload(600),
319 )
320}
321
322fn prepare_county(
323 view: &SimulationView<'_>,
324 context: &BoundaryContext,
325) -> Result<BoundaryProposal, CanwuError> {
326 prepare_owner(
327 view,
328 context,
329 COUNTY_PLUGIN,
330 "prepare-county",
331 county_reference(),
332 &action_payload(600),
333 )
334}
335
336fn prepare_owner<T: DomainRecordType<Payload = ReliefAction>>(
337 view: &SimulationView<'_>,
338 context: &BoundaryContext,
339 plugin: &str,
340 system: &str,
341 reference: TypedDomainRecordRef<T>,
342 payload: &ReliefAction,
343) -> Result<BoundaryProposal, CanwuError> {
344 let Some(order_id) = owned_order_ingress(view, context, plugin, EXECUTE_INGRESS)? else {
345 return Ok(BoundaryProposal::default());
346 };
347 if order_id != ORDER_ID {
348 return Err(CanwuError::new(
349 ErrorCode::InvalidBoundary,
350 format!("{plugin} received an ingress for the wrong order"),
351 ));
352 }
353 let order = order_payload(view)?;
354 let hash = action_hash(payload.grain_units)?;
355 validate_order(&order, system, plugin, &hash)?;
356 let record = DomainRecordDraft::from_typed(reference, payload)?;
357 Ok(BoundaryProposal {
358 directives: vec![BoundaryDirective::MutateRecord {
359 mutation: DomainRecordMutation::Create { record },
360 summary: format!("{plugin} prepares its guarded relief disposition"),
361 }],
362 ..BoundaryProposal::default()
363 })
364}
365
366fn audit_order(
367 view: &SimulationView<'_>,
368 context: &BoundaryContext,
369) -> Result<BoundaryProposal, CanwuError> {
370 if owned_order_ingress(view, context, CENTRAL_PLUGIN, ISSUE_INGRESS)?.is_some() {
371 return Ok(BoundaryProposal::default());
372 }
373 let order = order_payload(view)?;
374 for (label, reference, expected_plugin, expected_system) in [
375 (
376 "treasury",
377 treasury_reference().as_untyped(),
378 TREASURY_PLUGIN,
379 "prepare-treasury",
380 ),
381 (
382 "county",
383 county_reference().as_untyped(),
384 COUNTY_PLUGIN,
385 "prepare-county",
386 ),
387 ] {
388 let record = view.domain_record(reference)?.ok_or_else(|| {
389 CanwuError::new(
390 ErrorCode::InvalidBoundary,
391 format!("central audit is missing the {label} relief disposition"),
392 )
393 })?;
394 let expected_hash = action_hash(600)?;
395 validate_order(&order, expected_system, expected_plugin, &expected_hash)?;
396 let actual_hash = canonical_hash(ACTION_HASH_DOMAIN, &record.payload)?;
397 if record.version != 1
398 || !record.is_active()
399 || record.owner != expected_plugin
400 || actual_hash != expected_hash
401 || record.payload != json!({"status": "committed", "grain_units": 600})
402 {
403 return Err(CanwuError::new(
404 ErrorCode::InvalidBoundary,
405 format!("central audit found an invalid {label} disposition"),
406 ));
407 }
408 }
409 Ok(BoundaryProposal::default())
410}More examples
examples/plugin.rs (lines 23-26)
11fn set_stance(
12 view: &SimulationView<'_>,
13 context: &CommandContext,
14 payload: &Value,
15) -> Result<Vec<SystemDirective>, CanwuError> {
16 let army = ArmyId::new(
17 payload
18 .get("army")
19 .and_then(Value::as_u64)
20 .expect("payload was validated before the handler ran"),
21 );
22 let Some(army_state) = view.army(army)? else {
23 return Err(CanwuError::new(
24 ErrorCode::ArmyNotFound,
25 format!("army {army} was not found"),
26 ));
27 };
28 if context.issuer != Issuer::Actor(army_state.commander) {
29 return Err(CanwuError::new(
30 ErrorCode::InvalidAuthority,
31 "only the army commander may set its stance",
32 ));
33 }
34 Ok(vec![SystemDirective::SetComponent {
35 state: StateKey::new("military", "stance"),
36 entity: EntityRef::Army(army),
37 component: "stance".to_owned(),
38 value: payload["stance"].clone(),
39 summary: format!("Army {army} changed stance"),
40 }])
41}examples/phased_boundary.rs (lines 54-57)
48fn apply_grain(
49 view: &SimulationView<'_>,
50 _context: &BoundaryContext,
51) -> Result<BoundaryProposal, CanwuError> {
52 let reservation = ReservationRef::new("example-demand", "request", "daily-grain");
53 let allocation = view.reservation(&reservation)?.ok_or_else(|| {
54 CanwuError::new(
55 ErrorCode::InvalidBoundary,
56 "the declared grain reservation did not produce allocation evidence",
57 )
58 })?;
59 let territory = EntityRef::Territory(TerritoryId::new(1));
60 Ok(BoundaryProposal {
61 directives: vec![
62 BoundaryDirective::SetComponent {
63 state: StateKey::new("garrison", "grain"),
64 entity: territory.clone(),
65 component: "daily_grant".to_owned(),
66 value: Value::from(allocation.granted),
67 summary: format!(
68 "Granted {} grain to the western garrison",
69 allocation.granted
70 ),
71 },
72 BoundaryDirective::Emit {
73 event_type: "grain_allocated".to_owned(),
74 summary: "The daily grain allocation settled".to_owned(),
75 affected: vec![territory],
76 },
77 ],
78 ..BoundaryProposal::default()
79 })
80}pub fn with_entity(self, entity: EntityRef) -> CanwuError
Trait Implementations§
Source§impl Clone for CanwuError
impl Clone for CanwuError
Source§fn clone(&self) -> CanwuError
fn clone(&self) -> CanwuError
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for CanwuError
impl Debug for CanwuError
Source§impl<'de> Deserialize<'de> for CanwuError
impl<'de> Deserialize<'de> for CanwuError
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<CanwuError, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<CanwuError, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Deserialize this value from the given Serde deserializer. Read more
Source§impl Display for CanwuError
impl Display for CanwuError
impl Eq for CanwuError
Source§impl Error for CanwuError
impl Error for CanwuError
1.30.0 · Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
👎Deprecated since 1.42.0:
use the Display impl or to_string()
Source§impl PartialEq for CanwuError
impl PartialEq for CanwuError
Source§impl Serialize for CanwuError
impl Serialize for CanwuError
Source§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Serialize this value into the given Serde serializer. Read more
impl StructuralPartialEq for CanwuError
Auto Trait Implementations§
impl Freeze for CanwuError
impl RefUnwindSafe for CanwuError
impl Send for CanwuError
impl Sync for CanwuError
impl Unpin for CanwuError
impl UnsafeUnpin for CanwuError
impl UnwindSafe for CanwuError
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more