greentic_component/
error.rs1use displaydoc::Display;
2
3#[cfg(feature = "abi")]
4use crate::abi::AbiError;
5use crate::capabilities::CapabilityError;
6#[cfg(feature = "describe")]
7use crate::describe::DescribeError;
8use crate::limits::LimitError;
9#[cfg(feature = "loader")]
10use crate::loader::LoadError;
11use crate::manifest::ManifestError;
12use crate::schema::SchemaIntrospectionError;
13use crate::signing::SigningError;
14
15#[derive(Debug, Display, thiserror::Error)]
16pub enum ComponentError {
17 Manifest(#[from] ManifestError),
19 SchemaIntrospection(#[from] SchemaIntrospectionError),
21 #[cfg(feature = "abi")]
23 Abi(#[from] AbiError),
24 #[cfg(feature = "describe")]
26 Describe(#[from] DescribeError),
27 #[cfg(feature = "loader")]
29 Load(#[from] LoadError),
30 Capability(#[from] CapabilityError),
32 Limits(#[from] LimitError),
34 Signing(#[from] SigningError),
36 Io(#[from] std::io::Error),
38 Doctor(String),
40 SchemaQualityEmpty {
42 component: String,
43 operation: String,
44 direction: &'static str,
45 suggestion: String,
46 },
47}
48
49impl ComponentError {
50 pub fn code(&self) -> &'static str {
51 match self {
52 ComponentError::Manifest(_) => "manifest-invalid",
53 ComponentError::SchemaIntrospection(_) => "schema-introspection",
54 #[cfg(feature = "abi")]
55 ComponentError::Abi(err) => match err {
56 crate::abi::AbiError::WorldMismatch { .. } => "world-mismatch",
57 crate::abi::AbiError::MissingWasiTarget => "wasi-target-missing",
58 _ => "abi-error",
59 },
60 #[cfg(feature = "describe")]
61 ComponentError::Describe(err) => match err {
62 crate::describe::DescribeError::NotFound(_) => "describe-missing",
63 crate::describe::DescribeError::Json { .. } => "describe-invalid",
64 crate::describe::DescribeError::Io { .. } => "describe-io",
65 crate::describe::DescribeError::Metadata(_) => "describe-metadata",
66 },
67 #[cfg(feature = "loader")]
68 ComponentError::Load(_) => "component-load",
69 ComponentError::Capability(_) => "capability-error",
70 ComponentError::Limits(_) => "limits-error",
71 ComponentError::Signing(_) => "hash-mismatch",
72 ComponentError::Io(_) => "io-error",
73 ComponentError::Doctor(_) => "doctor-failure",
74 ComponentError::SchemaQualityEmpty { .. } => "E_OP_SCHEMA_EMPTY",
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn code_maps_key_error_variants_to_stable_strings() {
85 assert_eq!(
86 ComponentError::Doctor("oops".into()).code(),
87 "doctor-failure"
88 );
89 assert_eq!(
90 ComponentError::SchemaQualityEmpty {
91 component: "demo".into(),
92 operation: "run".into(),
93 direction: "input",
94 suggestion: "add fields".into(),
95 }
96 .code(),
97 "E_OP_SCHEMA_EMPTY"
98 );
99 assert_eq!(
100 ComponentError::Capability(CapabilityError::invalid("host.http", "denied")).code(),
101 "capability-error"
102 );
103 assert_eq!(
104 ComponentError::Signing(SigningError::HashMismatch {
105 expected: "a".into(),
106 found: "b".into(),
107 })
108 .code(),
109 "hash-mismatch"
110 );
111 }
112
113 #[test]
114 fn code_distinguishes_abi_and_describe_subclasses() {
115 #[cfg(feature = "abi")]
116 {
117 assert_eq!(
118 ComponentError::Abi(AbiError::MissingWasiTarget).code(),
119 "wasi-target-missing"
120 );
121 }
122 #[cfg(feature = "describe")]
123 {
124 assert_eq!(
125 ComponentError::Describe(DescribeError::NotFound("describe".into())).code(),
126 "describe-missing"
127 );
128 }
129 #[cfg(feature = "loader")]
130 {
131 assert_eq!(
132 ComponentError::Load(LoadError::NotFound("demo".into())).code(),
133 "component-load"
134 );
135 }
136 }
137}