Skip to main content

lenso_app_plan/
error.rs

1use std::{fmt, time::Duration};
2
3use super::CapabilityOperationKind;
4
5/// A reason App Composition could not be materialized into a Plan.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum PlanResolutionError {
8    /// The Plan schema cannot be executed by this Kernel version.
9    UnsupportedSchemaVersion { expected: u32, actual: u32 },
10    /// Two Plugin Instances use the same App-local key.
11    DuplicatePluginInstance { instance_key: String },
12    /// Every App must declare at least one Execution Lane.
13    MissingExecutionLane,
14    /// An Execution Lane identity is empty or whitespace-only.
15    InvalidExecutionLane { execution_lane: String },
16    /// Two Execution Lanes use the same App-local identity.
17    DuplicateExecutionLane { execution_lane: String },
18    /// A Plugin Instance names an Execution Lane absent from the Plan.
19    UndeclaredExecutionLane {
20        instance_key: String,
21        execution_lane: String,
22    },
23    /// A Plugin Instance has no executable entrypoint identity.
24    InvalidPluginEntrypoint { instance_key: String },
25    /// A Plugin declares the same provided Capability more than once.
26    DuplicateProvidedCapability {
27        provider_instance: String,
28        capability_id: String,
29    },
30    /// One endpoint declares an Operation more than once.
31    DuplicateOperation {
32        provider_instance: String,
33        capability_id: String,
34        operation: String,
35    },
36    /// A Plugin declares the same required Capability more than once.
37    DuplicateRequiredCapability {
38        consumer_instance: String,
39        capability_id: String,
40    },
41    /// A binding names a consumer that is not in the Composition.
42    InvalidConsumerReference {
43        consumer_instance: String,
44        capability_id: String,
45    },
46    /// A binding names a provider that is not in the Composition or does not provide the Capability.
47    InvalidProviderReference {
48        consumer_instance: String,
49        capability_id: String,
50        provider_instance: String,
51    },
52    /// A binding exists without a matching consumer requirement.
53    UndeclaredCapabilityRequirement {
54        consumer_instance: String,
55        capability_id: String,
56    },
57    /// The provider and consumer selected different exact Descriptor versions.
58    IncompatibleCapabilityVersion {
59        consumer_instance: String,
60        capability_id: String,
61        required: String,
62        provided: String,
63        provider_instance: String,
64    },
65    /// A binding crosses Execution Lanes but its generated contract types are not transferable.
66    CrossLaneTransferUnsupported {
67        consumer_instance: String,
68        provider_instance: String,
69        capability_id: String,
70    },
71    /// A host Runtime does not implement cross-lane transfer for the selected interaction kind.
72    ///
73    /// Retained so downstream code can classify failures produced by older Runtime versions.
74    CrossLaneInteractionUnsupported {
75        capability_id: String,
76        operation: String,
77        interaction: CapabilityOperationKind,
78    },
79    /// A `one` requirement has no explicit provider.
80    MissingOneBinding {
81        consumer_instance: String,
82        capability_id: String,
83    },
84    /// A `one` requirement has more than one explicit provider.
85    AmbiguousOneBinding {
86        consumer_instance: String,
87        capability_id: String,
88        providers: usize,
89    },
90    /// An `optional` requirement has more than one explicit provider.
91    AmbiguousOptionalBinding {
92        consumer_instance: String,
93        capability_id: String,
94        providers: usize,
95    },
96    /// A provider is repeated for the same requirement.
97    DuplicateBinding {
98        consumer_instance: String,
99        capability_id: String,
100        provider_instance: String,
101    },
102    /// A request Operation has an invalid bounded admission policy.
103    InvalidRequestAdmission {
104        capability_id: String,
105        operation: String,
106        queue_capacity: usize,
107        max_concurrency: usize,
108    },
109    /// Admission was configured for an Operation absent from the endpoint.
110    UnknownAdmissionOperation {
111        capability_id: String,
112        operation: String,
113    },
114    /// Interaction metadata was configured for an absent Operation.
115    UnknownOperationInteraction {
116        capability_id: String,
117        operation: String,
118    },
119    /// A Plugin Instance selected an unusable finite restart policy.
120    InvalidRestartPolicy {
121        instance_key: String,
122        max_attempts: usize,
123        window: Duration,
124    },
125    /// Explicit Capability activation dependencies contain a cycle.
126    ActivationCycle { instances: Vec<String> },
127}
128
129impl fmt::Display for PlanResolutionError {
130    #[allow(clippy::too_many_lines)]
131    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            Self::UnsupportedSchemaVersion { expected, actual } => write!(
134                formatter,
135                "unsupported Plan schema version {actual}; expected {expected}"
136            ),
137            Self::DuplicatePluginInstance { instance_key } => {
138                write!(formatter, "duplicate Plugin Instance `{instance_key}`")
139            }
140            Self::MissingExecutionLane => {
141                formatter.write_str("Resolved App Plan declares no Execution Lanes")
142            }
143            Self::InvalidExecutionLane { execution_lane } => {
144                write!(formatter, "invalid Execution Lane `{execution_lane}`")
145            }
146            Self::DuplicateExecutionLane { execution_lane } => {
147                write!(formatter, "duplicate Execution Lane `{execution_lane}`")
148            }
149            Self::UndeclaredExecutionLane {
150                instance_key,
151                execution_lane,
152            } => write!(
153                formatter,
154                "Plugin Instance `{instance_key}` is placed on undeclared Execution Lane `{execution_lane}`"
155            ),
156            Self::InvalidPluginEntrypoint { instance_key } => write!(
157                formatter,
158                "Plugin Instance `{instance_key}` has an empty entrypoint"
159            ),
160            Self::DuplicateProvidedCapability {
161                provider_instance,
162                capability_id,
163            } => write!(
164                formatter,
165                "Plugin Instance `{provider_instance}` provides Capability `{capability_id}` more than once"
166            ),
167            Self::DuplicateOperation {
168                provider_instance,
169                capability_id,
170                operation,
171            } => write!(
172                formatter,
173                "Plugin Instance `{provider_instance}` Capability `{capability_id}` declares Operation `{operation}` more than once"
174            ),
175            Self::DuplicateRequiredCapability {
176                consumer_instance,
177                capability_id,
178            } => write!(
179                formatter,
180                "Plugin Instance `{consumer_instance}` requires Capability `{capability_id}` more than once"
181            ),
182            Self::InvalidConsumerReference {
183                consumer_instance,
184                capability_id,
185            } => write!(
186                formatter,
187                "Capability `{capability_id}` names missing consumer `{consumer_instance}`"
188            ),
189            Self::InvalidProviderReference {
190                consumer_instance,
191                capability_id,
192                provider_instance,
193            } => write!(
194                formatter,
195                "consumer `{consumer_instance}` names invalid provider `{provider_instance}` for Capability `{capability_id}`"
196            ),
197            Self::UndeclaredCapabilityRequirement {
198                consumer_instance,
199                capability_id,
200            } => write!(
201                formatter,
202                "consumer `{consumer_instance}` has no declared requirement for Capability `{capability_id}`"
203            ),
204            Self::IncompatibleCapabilityVersion {
205                consumer_instance,
206                capability_id,
207                required,
208                provided,
209                provider_instance,
210            } => write!(
211                formatter,
212                "consumer `{consumer_instance}` requires Capability `{capability_id}` version `{required}`, but provider `{provider_instance}` provides `{provided}`"
213            ),
214            Self::CrossLaneTransferUnsupported {
215                consumer_instance,
216                provider_instance,
217                capability_id,
218            } => write!(
219                formatter,
220                "consumer `{consumer_instance}` binds Capability `{capability_id}` across Execution Lanes to provider `{provider_instance}`, but its contract types do not support cross-lane transfer"
221            ),
222            Self::CrossLaneInteractionUnsupported {
223                capability_id,
224                operation,
225                interaction,
226            } => write!(
227                formatter,
228                "Capability `{capability_id}` Operation `{operation}` uses {interaction:?}, which this Plan version cannot transfer across Execution Lanes"
229            ),
230            Self::MissingOneBinding {
231                consumer_instance,
232                capability_id,
233            } => write!(
234                formatter,
235                "consumer `{consumer_instance}` is missing one binding for Capability `{capability_id}`"
236            ),
237            Self::AmbiguousOneBinding {
238                consumer_instance,
239                capability_id,
240                providers,
241            } => write!(
242                formatter,
243                "consumer `{consumer_instance}` has {providers} bindings for one Capability `{capability_id}`"
244            ),
245            Self::AmbiguousOptionalBinding {
246                consumer_instance,
247                capability_id,
248                providers,
249            } => write!(
250                formatter,
251                "consumer `{consumer_instance}` has {providers} bindings for optional Capability `{capability_id}`"
252            ),
253            Self::DuplicateBinding {
254                consumer_instance,
255                capability_id,
256                provider_instance,
257            } => write!(
258                formatter,
259                "consumer `{consumer_instance}` binds Capability `{capability_id}` to provider `{provider_instance}` more than once"
260            ),
261            Self::InvalidRequestAdmission {
262                capability_id,
263                operation,
264                queue_capacity,
265                max_concurrency,
266            } => write!(
267                formatter,
268                "Capability `{capability_id}` Operation `{operation}` has invalid request admission (queue capacity {queue_capacity}, concurrency {max_concurrency})"
269            ),
270            Self::UnknownAdmissionOperation {
271                capability_id,
272                operation,
273            } => write!(
274                formatter,
275                "Capability `{capability_id}` configures request admission for unknown Operation `{operation}`"
276            ),
277            Self::UnknownOperationInteraction {
278                capability_id,
279                operation,
280            } => write!(
281                formatter,
282                "Capability `{capability_id}` configures interaction metadata for unknown Operation `{operation}`"
283            ),
284            Self::InvalidRestartPolicy {
285                instance_key,
286                max_attempts,
287                window,
288            } => write!(
289                formatter,
290                "Plugin Instance `{instance_key}` has invalid restart policy (attempts {max_attempts}, window {window:?})"
291            ),
292            Self::ActivationCycle { instances } => write!(
293                formatter,
294                "required Capability activation cycle: {}",
295                instances.join(" -> ")
296            ),
297        }
298    }
299}
300
301impl std::error::Error for PlanResolutionError {}