Skip to main content

greentic_component_runtime/
error.rs

1use component_manifest::ManifestError;
2use greentic_component_store::StoreError;
3use jsonschema::ValidationError;
4use thiserror::Error;
5use wasmtime::Error as WasmtimeError;
6
7#[derive(Debug, Error)]
8pub enum CompError {
9    #[error(transparent)]
10    Store(#[from] StoreError),
11    #[error(transparent)]
12    Manifest(#[from] ManifestError),
13    #[error(transparent)]
14    Json(#[from] serde_json::Error),
15    #[error(transparent)]
16    Wasmtime(#[from] WasmtimeError),
17    #[error("schema validation failed: {0}")]
18    SchemaValidation(String),
19    #[error("binding not found for tenant {0}")]
20    BindingNotFound(String),
21    #[error("secret `{0}` is not declared by the component")]
22    SecretNotDeclared(String),
23    #[error("secret `{key}` resolution failed: {source}")]
24    SecretResolution {
25        key: String,
26        #[source]
27        source: Box<CompError>,
28    },
29    #[error("operation `{0}` is not exported by the component")]
30    OperationNotFound(String),
31    #[error("host feature `{0}` is denied by policy")]
32    HostFeatureDenied(&'static str),
33    #[error("invalid manifest: {0}")]
34    InvalidManifest(&'static str),
35    #[error("runtime error: {0}")]
36    Runtime(String),
37}
38
39impl<'a> From<ValidationError<'a>> for CompError {
40    fn from(value: ValidationError<'a>) -> Self {
41        CompError::SchemaValidation(value.to_string())
42    }
43}
44
45impl CompError {
46    pub fn secret_resolution(key: impl Into<String>, source: CompError) -> Self {
47        CompError::SecretResolution {
48            key: key.into(),
49            source: Box::new(source),
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use jsonschema::validator_for;
58    use serde_json::json;
59
60    #[test]
61    fn validation_errors_are_normalized_to_schema_validation() {
62        let schema = validator_for(&json!({
63            "type": "object",
64            "properties": { "enabled": { "type": "boolean" } },
65            "required": ["enabled"],
66            "additionalProperties": false
67        }))
68        .expect("validator");
69
70        let invalid = json!({"enabled": "yes"});
71        let mut errors = schema.iter_errors(&invalid);
72        let err = errors.next().expect("validation error");
73        let comp_error = CompError::from(err);
74
75        assert!(
76            matches!(comp_error, CompError::SchemaValidation(message) if message.contains("boolean"))
77        );
78    }
79
80    #[test]
81    fn secret_resolution_keeps_the_failing_key() {
82        let err =
83            CompError::secret_resolution("API_TOKEN", CompError::Runtime("vault offline".into()));
84
85        assert!(matches!(
86            err,
87            CompError::SecretResolution { ref key, ref source }
88                if key == "API_TOKEN" && matches!(source.as_ref(), CompError::Runtime(message) if message == "vault offline")
89        ));
90    }
91}