Skip to main content

coil_wasm/
error.rs

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 {}