1use std::{fmt, time::Duration};
2
3use super::CapabilityOperationKind;
4
5#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum PlanResolutionError {
8 UnsupportedSchemaVersion { expected: u32, actual: u32 },
10 DuplicatePluginInstance { instance_key: String },
12 MissingExecutionLane,
14 InvalidExecutionLane { execution_lane: String },
16 DuplicateExecutionLane { execution_lane: String },
18 UndeclaredExecutionLane {
20 instance_key: String,
21 execution_lane: String,
22 },
23 InvalidPluginEntrypoint { instance_key: String },
25 DuplicateProvidedCapability {
27 provider_instance: String,
28 capability_id: String,
29 },
30 DuplicateOperation {
32 provider_instance: String,
33 capability_id: String,
34 operation: String,
35 },
36 DuplicateRequiredCapability {
38 consumer_instance: String,
39 capability_id: String,
40 },
41 InvalidConsumerReference {
43 consumer_instance: String,
44 capability_id: String,
45 },
46 InvalidProviderReference {
48 consumer_instance: String,
49 capability_id: String,
50 provider_instance: String,
51 },
52 UndeclaredCapabilityRequirement {
54 consumer_instance: String,
55 capability_id: String,
56 },
57 IncompatibleCapabilityVersion {
59 consumer_instance: String,
60 capability_id: String,
61 required: String,
62 provided: String,
63 provider_instance: String,
64 },
65 CrossLaneTransferUnsupported {
67 consumer_instance: String,
68 provider_instance: String,
69 capability_id: String,
70 },
71 CrossLaneInteractionUnsupported {
75 capability_id: String,
76 operation: String,
77 interaction: CapabilityOperationKind,
78 },
79 MissingOneBinding {
81 consumer_instance: String,
82 capability_id: String,
83 },
84 AmbiguousOneBinding {
86 consumer_instance: String,
87 capability_id: String,
88 providers: usize,
89 },
90 AmbiguousOptionalBinding {
92 consumer_instance: String,
93 capability_id: String,
94 providers: usize,
95 },
96 DuplicateBinding {
98 consumer_instance: String,
99 capability_id: String,
100 provider_instance: String,
101 },
102 InvalidRequestAdmission {
104 capability_id: String,
105 operation: String,
106 queue_capacity: usize,
107 max_concurrency: usize,
108 },
109 UnknownAdmissionOperation {
111 capability_id: String,
112 operation: String,
113 },
114 UnknownOperationInteraction {
116 capability_id: String,
117 operation: String,
118 },
119 InvalidRestartPolicy {
121 instance_key: String,
122 max_attempts: usize,
123 window: Duration,
124 },
125 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 {}