bb_runtime/errors/delivery.rs
1//! `DeliveryError`
2//!
3//! Returned by the host-facing `Node::deliver_inbound` /
4//! `deliver_event` / `invoke` entry points when delivery cannot be
5//! enqueued onto the ingress.
6
7use crate::bus::AllocFailReason;
8
9/// Errors surfaced by host-facing delivery methods on `Node`.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum DeliveryError {
12 /// The ingress queue is closed (Node is shutting down).
13 IngressClosed,
14
15 /// `deliver_event` / `invoke` referenced an unknown module name.
16 UnknownModule(String),
17
18 /// `deliver_event` referenced a module that exists but has no
19 /// such input port.
20 UnknownInput {
21 /// Module name (resolved successfully).
22 module: String,
23 /// Input port name (not found on the module).
24 input: String,
25 },
26
27 /// `deliver_inbound` received bytes that failed
28 /// `EnvelopeCodec::decode_capped` — malformed prost frame,
29 /// schema-version mismatch, or one of the `NodeConfig.envelope_caps`
30 /// limits exceeded.
31 InvalidEnvelope(String),
32
33 /// `deliver_event` / `invoke` payload exceeded the configured
34 /// per-item cap (`NodeConfig::max_app_event_bytes` or
35 /// `NodeConfig::max_invoke_bytes`). A matching
36 /// `InfraEvent::AppIngressError` lands on the bus alongside this
37 /// synchronous return so observers see the per-item rejection.
38 OversizePayload {
39 /// Bytes the caller attempted to admit.
40 byte_count: usize,
41 /// Cap value the boundary enforced.
42 cap: usize,
43 },
44
45 /// `invoke` carried more `(name, bytes)` inputs than the
46 /// configured `NodeConfig::max_invoke_inputs` cap allowed.
47 /// Matching `InfraEvent::AppIngressError` lands on the bus.
48 TooManyInputs {
49 /// Inputs the caller attempted to admit.
50 count: usize,
51 /// Cap value the boundary enforced.
52 cap: usize,
53 },
54
55 /// `deliver_event` / `invoke` could not allocate the
56 /// framework-owned buffer needed to hold the caller's payload,
57 /// either because `Vec::try_reserve_exact` returned
58 /// `TryReserveError` or because admitting the payload would
59 /// exceed `NodeConfig::ingress_byte_budget`. Matching
60 /// `InfraEvent::AppIngressError` lands on the bus.
61 AllocationFailed {
62 /// Bytes the boundary tried to admit.
63 byte_count: usize,
64 /// Why the reservation failed.
65 reason: AllocFailReason,
66 },
67
68 /// Admitting this payload would push the engine over
69 /// `NodeConfig::ingress_byte_budget`. Matching
70 /// `InfraEvent::AppIngressError` lands on the bus.
71 BudgetExceeded {
72 /// Bytes the boundary tried to admit.
73 byte_count: usize,
74 /// Bytes still available under the configured budget at the
75 /// time of the rejection.
76 budget_remaining: usize,
77 },
78}
79
80impl std::fmt::Display for DeliveryError {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 match self {
83 Self::IngressClosed => write!(f, "ingress queue closed"),
84 Self::UnknownModule(name) => write!(f, "unknown module: {name}"),
85 Self::UnknownInput { module, input } => {
86 write!(f, "module {module} has no input port '{input}'")
87 }
88 Self::InvalidEnvelope(detail) => {
89 write!(f, "inbound envelope rejected: {detail}")
90 }
91 Self::OversizePayload { byte_count, cap } => {
92 write!(f, "payload of {byte_count} bytes exceeds cap of {cap}")
93 }
94 Self::TooManyInputs { count, cap } => {
95 write!(f, "{count} inputs exceeds cap of {cap}")
96 }
97 Self::AllocationFailed { byte_count, reason } => match reason {
98 AllocFailReason::HeapExhausted => {
99 write!(f, "heap exhausted reserving {byte_count} bytes")
100 }
101 AllocFailReason::PerItemCapExceeded { cap } => {
102 write!(
103 f,
104 "per-item cap {cap} rejected payload of {byte_count} bytes"
105 )
106 }
107 },
108 Self::BudgetExceeded {
109 byte_count,
110 budget_remaining,
111 } => write!(
112 f,
113 "ingress budget exceeded: {byte_count} bytes requested, {budget_remaining} remaining"
114 ),
115 }
116 }
117}
118
119impl std::error::Error for DeliveryError {}
120