1use std::error::Error;
2use std::fmt;
3
4use crate::grants::HostCapabilityGrant;
5use crate::host_api::HostServiceDomain;
6use crate::ids::{ContractVersion, ExtensionPointKind, HttpMethod};
7use crate::invocation::PrincipalKind;
8use crate::manifest::ExtensionConfigValueType;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum WasmModelError {
12 EmptyField {
13 field: &'static str,
14 },
15 InvalidToken {
16 field: &'static str,
17 value: String,
18 },
19 InvalidChecksum {
20 field: &'static str,
21 value: String,
22 },
23 ArtifactRead {
24 path: String,
25 reason: String,
26 },
27 ArtifactChecksumMismatch {
28 path: String,
29 expected: String,
30 actual: String,
31 },
32 DuplicateConfigField {
33 key: String,
34 },
35 UnknownConfigField {
36 key: String,
37 },
38 MissingRequiredConfigField {
39 key: String,
40 },
41 ConfigTypeMismatch {
42 key: String,
43 expected: ExtensionConfigValueType,
44 actual: ExtensionConfigValueType,
45 },
46 InvalidConfigValue {
47 key: String,
48 reason: String,
49 },
50 DuplicateJsonLdProperty {
51 property: String,
52 },
53 InvalidJsonLdProperty {
54 property: String,
55 },
56 InvalidJsonLdNumber {
57 property: String,
58 value: String,
59 },
60 InvalidRoute {
61 field: &'static str,
62 route: String,
63 },
64 DuplicateHandlerId {
65 handler_id: String,
66 },
67 UnsupportedPageMethod {
68 method: HttpMethod,
69 },
70 UnsupportedGrantForPoint {
71 handler_id: String,
72 point: ExtensionPointKind,
73 grant: HostCapabilityGrant,
74 },
75 HandlerNotFound {
76 handler_id: String,
77 },
78 DuplicateInstalledHandler {
79 handler_id: String,
80 },
81 DuplicateInstalledExtension {
82 extension_id: String,
83 },
84 MixedCustomerAppInstallation {
85 extension_id: String,
86 expected: String,
87 actual: String,
88 },
89 GrantNotDeclared {
90 handler_id: String,
91 grant: HostCapabilityGrant,
92 },
93 HostApiVersionMismatch {
94 extension_id: String,
95 expected: ContractVersion,
96 actual: ContractVersion,
97 },
98 DuplicateExtensionTarget {
99 point: ExtensionPointKind,
100 target: String,
101 existing_handler: String,
102 conflicting_handler: String,
103 },
104 LimitOverrideExceedsDeclared {
105 handler_id: String,
106 field: &'static str,
107 },
108 ZeroLimit {
109 field: &'static str,
110 },
111 PrincipalIdRequired {
112 kind: PrincipalKind,
113 },
114 InvocationPointMismatch {
115 handler_id: String,
116 expected: ExtensionPointKind,
117 actual: ExtensionPointKind,
118 },
119 InvocationTargetMismatch {
120 handler_id: String,
121 detail: String,
122 },
123 UnverifiedWebhook {
124 handler_id: String,
125 },
126 ReplayUnsafeWebhook {
127 handler_id: String,
128 },
129 HostGrantDenied {
130 handler_id: String,
131 grant: HostCapabilityGrant,
132 },
133 HostServiceUnavailable {
134 handler_id: String,
135 domain: HostServiceDomain,
136 reason: String,
137 },
138 ResourceLimitExceeded {
139 handler_id: String,
140 field: &'static str,
141 },
142 ZeroSchemaVersion {
143 field: &'static str,
144 },
145 InvalidOutcomeForPoint {
146 handler_id: String,
147 point: ExtensionPointKind,
148 outcome: &'static str,
149 },
150 RuntimeBudgetExceeded {
151 handler_id: String,
152 max_runtime: std::time::Duration,
153 actual_runtime: std::time::Duration,
154 },
155 EngineCompile {
156 reason: String,
157 },
158 EngineInstantiate {
159 handler_id: String,
160 reason: String,
161 },
162 EngineExportMissing {
163 handler_id: String,
164 export: String,
165 },
166 EngineTrap {
167 handler_id: String,
168 reason: String,
169 },
170 InvalidHostCapabilitySlot {
171 handler_id: String,
172 slot: i32,
173 },
174 InvalidHostCallMetric {
175 handler_id: String,
176 metric: i64,
177 },
178 InvalidOutcomeCode {
179 handler_id: String,
180 code: i32,
181 },
182 InvalidTypedStatus {
183 status: u16,
184 },
185 InvalidTypedReturn {
186 reason: String,
187 },
188 TypedReturnPointMismatch {
189 expected: ExtensionPointKind,
190 actual: ExtensionPointKind,
191 },
192 TypedReturnBodyMismatch {
193 point: ExtensionPointKind,
194 body: String,
195 },
196}
197
198impl fmt::Display for WasmModelError {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 Self::EmptyField { field } => write!(f, "`{field}` cannot be empty"),
202 Self::InvalidToken { field, value } => {
203 write!(f, "`{field}` contains an invalid token `{value}`")
204 }
205 Self::InvalidChecksum { field, value } => write!(
206 f,
207 "`{field}` must be a 64-character lowercase hex digest, got `{value}`"
208 ),
209 Self::ArtifactRead { path, reason } => {
210 write!(f, "failed to read artifact at `{path}`: {reason}")
211 }
212 Self::ArtifactChecksumMismatch {
213 path,
214 expected,
215 actual,
216 } => write!(
217 f,
218 "artifact at `{path}` failed checksum verification: expected `{expected}`, got `{actual}`"
219 ),
220 Self::DuplicateConfigField { key } => {
221 write!(f, "extension config schema declares duplicate key `{key}`")
222 }
223 Self::UnknownConfigField { key } => {
224 write!(
225 f,
226 "extension config field `{key}` is not declared in the package schema"
227 )
228 }
229 Self::MissingRequiredConfigField { key } => {
230 write!(
231 f,
232 "extension config field `{key}` is required but was not provided"
233 )
234 }
235 Self::ConfigTypeMismatch {
236 key,
237 expected,
238 actual,
239 } => write!(
240 f,
241 "extension config field `{key}` expects `{expected}` but received `{actual}`"
242 ),
243 Self::InvalidConfigValue { key, reason } => {
244 write!(f, "extension config field `{key}` is invalid: {reason}")
245 }
246 Self::DuplicateJsonLdProperty { property } => {
247 write!(f, "JSON-LD property `{property}` is duplicated")
248 }
249 Self::InvalidJsonLdProperty { property } => {
250 write!(f, "JSON-LD property `{property}` is invalid")
251 }
252 Self::InvalidJsonLdNumber { property, value } => {
253 write!(
254 f,
255 "JSON-LD property `{property}` expects a finite number, got `{value}`"
256 )
257 }
258 Self::InvalidRoute { field, route } => {
259 write!(f, "`{field}` must start with `/`, got `{route}`")
260 }
261 Self::DuplicateHandlerId { handler_id } => {
262 write!(
263 f,
264 "extension manifest declares duplicate handler `{handler_id}`"
265 )
266 }
267 Self::UnsupportedPageMethod { method } => {
268 write!(f, "page handlers do not support `{method}`")
269 }
270 Self::UnsupportedGrantForPoint {
271 handler_id,
272 point,
273 grant,
274 } => write!(
275 f,
276 "handler `{handler_id}` for `{point}` cannot request host grant `{grant}`"
277 ),
278 Self::HandlerNotFound { handler_id } => {
279 write!(
280 f,
281 "installed handler `{handler_id}` does not exist in the manifest"
282 )
283 }
284 Self::DuplicateInstalledHandler { handler_id } => {
285 write!(f, "handler `{handler_id}` is installed more than once")
286 }
287 Self::DuplicateInstalledExtension { extension_id } => {
288 write!(f, "extension `{extension_id}` is installed more than once")
289 }
290 Self::MixedCustomerAppInstallation {
291 extension_id,
292 expected,
293 actual,
294 } => write!(
295 f,
296 "extension `{extension_id}` targets customer app `{actual}` but the registry is already bound to `{expected}`"
297 ),
298 Self::GrantNotDeclared { handler_id, grant } => write!(
299 f,
300 "handler `{handler_id}` was granted `{grant}` without declaring it in the manifest"
301 ),
302 Self::HostApiVersionMismatch {
303 extension_id,
304 expected,
305 actual,
306 } => write!(
307 f,
308 "extension `{extension_id}` requires host API `{actual}` but runtime provides `{expected}`"
309 ),
310 Self::DuplicateExtensionTarget {
311 point,
312 target,
313 existing_handler,
314 conflicting_handler,
315 } => write!(
316 f,
317 "extension target `{target}` for `{point}` is already claimed by `{existing_handler}` and cannot also register `{conflicting_handler}`"
318 ),
319 Self::LimitOverrideExceedsDeclared { handler_id, field } => write!(
320 f,
321 "handler `{handler_id}` has an installation limit override that is looser for `{field}`"
322 ),
323 Self::ZeroLimit { field } => write!(f, "`{field}` must be greater than zero"),
324 Self::PrincipalIdRequired { kind } => {
325 write!(f, "principal kind `{kind}` requires a non-empty id")
326 }
327 Self::InvocationPointMismatch {
328 handler_id,
329 expected,
330 actual,
331 } => write!(
332 f,
333 "handler `{handler_id}` expects invocation point `{expected}` but received `{actual}`"
334 ),
335 Self::InvocationTargetMismatch { handler_id, detail } => {
336 write!(
337 f,
338 "handler `{handler_id}` cannot handle this invocation: {detail}"
339 )
340 }
341 Self::UnverifiedWebhook { handler_id } => write!(
342 f,
343 "handler `{handler_id}` cannot run until the host verifies the webhook signature"
344 ),
345 Self::ReplayUnsafeWebhook { handler_id } => write!(
346 f,
347 "handler `{handler_id}` cannot run until replay protection has been applied"
348 ),
349 Self::HostGrantDenied { handler_id, grant } => write!(
350 f,
351 "handler `{handler_id}` attempted host call `{grant}` without a granted capability"
352 ),
353 Self::HostServiceUnavailable {
354 handler_id,
355 domain,
356 reason,
357 } => write!(
358 f,
359 "handler `{handler_id}` cannot use `{domain:?}` host service: {reason}"
360 ),
361 Self::ResourceLimitExceeded { handler_id, field } => write!(
362 f,
363 "handler `{handler_id}` exceeded its `{field}` resource limit"
364 ),
365 Self::ZeroSchemaVersion { field } => {
366 write!(f, "`{field}` must be greater than zero")
367 }
368 Self::InvalidOutcomeForPoint {
369 handler_id,
370 point,
371 outcome,
372 } => write!(
373 f,
374 "handler `{handler_id}` for `{point}` returned invalid outcome `{outcome}`"
375 ),
376 Self::RuntimeBudgetExceeded {
377 handler_id,
378 max_runtime,
379 actual_runtime,
380 } => write!(
381 f,
382 "handler `{handler_id}` exceeded runtime budget {:?} with {:?}",
383 max_runtime, actual_runtime
384 ),
385 Self::EngineCompile { reason } => {
386 write!(f, "wasm engine could not compile module: {reason}")
387 }
388 Self::EngineInstantiate { handler_id, reason } => write!(
389 f,
390 "handler `{handler_id}` could not be instantiated in the wasm engine: {reason}"
391 ),
392 Self::EngineExportMissing { handler_id, export } => write!(
393 f,
394 "handler `{handler_id}` expected wasm export `{export}` but it was not present"
395 ),
396 Self::EngineTrap { handler_id, reason } => write!(
397 f,
398 "handler `{handler_id}` trapped during wasm execution: {reason}"
399 ),
400 Self::InvalidHostCapabilitySlot { handler_id, slot } => write!(
401 f,
402 "handler `{handler_id}` requested undeclared host capability slot `{slot}`"
403 ),
404 Self::InvalidHostCallMetric { handler_id, metric } => write!(
405 f,
406 "handler `{handler_id}` supplied invalid host-call metric `{metric}`"
407 ),
408 Self::InvalidOutcomeCode { handler_id, code } => write!(
409 f,
410 "handler `{handler_id}` returned unknown wasm outcome code `{code}`"
411 ),
412 Self::InvalidTypedStatus { status } => {
413 write!(f, "typed HTTP status `{status}` is invalid")
414 }
415 Self::InvalidTypedReturn { reason } => {
416 write!(f, "typed return payload is invalid: {reason}")
417 }
418 Self::TypedReturnPointMismatch { expected, actual } => write!(
419 f,
420 "typed return payload targets `{actual}` but invocation point is `{expected}`"
421 ),
422 Self::TypedReturnBodyMismatch { point, body } => write!(
423 f,
424 "typed return payload body `{body}` is not valid for `{point}` invocations"
425 ),
426 }
427 }
428}
429
430impl Error for WasmModelError {}