1use std::path::PathBuf;
2
3#[derive(Debug, thiserror::Error)]
4pub enum ComponentError {
5 #[error("component `{component_id}` has no artifact for target `{target}`")]
6 ArtifactNotFound { component_id: String, target: String },
7 #[error(
8 "component `{component_id}` is not locked; the embedded components.lock.json is still \
9 the empty bootstrap lock `alef generate` writes before any artifact exists -- run \
10 `alef component lock` to populate it"
11 )]
12 ComponentsNotLocked { component_id: String },
13 #[error("component `{component}` is bundled for this target and cannot be downloaded")]
14 BundledComponentNotLoadable { component: String },
15 #[error("component `{component_id}` does not provide contract `{contract}`")]
16 ContractNotProvided { component_id: String, contract: String },
17 #[error("invalid SHA-256 digest `{0}`")]
18 InvalidDigest(String),
19 #[error("artifact digest mismatch: expected {expected}, got {actual}")]
20 DigestMismatch { expected: String, actual: String },
21 #[error("artifact size mismatch: expected {expected} bytes, got {actual} bytes")]
22 SizeMismatch { expected: u64, actual: u64 },
23 #[error("artifact signature is required")]
24 SignatureRequired,
25 #[error("invalid Ed25519 signature encoding")]
26 InvalidSignatureEncoding,
27 #[error("artifact signature verification failed")]
28 SignatureVerification,
29 #[error("component manifest is not in canonical JSON form")]
30 NonCanonicalManifest,
31 #[error("component manifest digest does not match the lock")]
32 ManifestDigestMismatch,
33 #[error("component manifest identity does not match the lock")]
34 ManifestIdentityMismatch,
35 #[error("component manifest schema or ABI version is unsupported")]
36 UnsupportedManifest,
37 #[error("component signature uses unsupported algorithm `{0}`")]
38 UnsupportedSignatureAlgorithm(String),
39 #[error("component signature key `{0}` is not embedded in the lock")]
40 UnknownSignatureKey(String),
41 #[error("component signature key does not match the lock entry")]
42 SignatureKeyMismatch,
43 #[error("unsafe archive entry `{0}`")]
44 UnsafeArchiveEntry(String),
45 #[error("component archive has more than {max} entries (found at least {actual})")]
46 TooManyArchiveEntries { max: usize, actual: usize },
47 #[error("extracted component archive entry exceeds the {limit}-byte size limit ({actual} bytes)")]
48 ExtractedSizeExceeded { limit: u64, actual: u64 },
49 #[error("artifact library path `{0}` is not a safe relative path")]
50 UnsafeLibraryPath(String),
51 #[error("component library is missing at `{0}`")]
52 MissingLibrary(PathBuf),
53 #[error("failed to download `{url}`: {message}")]
54 Download { url: String, message: String },
55 #[error("component `{component}` is not cached and network access is disabled (would fetch `{url}`)")]
56 Offline { component: String, url: String },
57 #[error("failed to load component library `{path}`: {message}")]
58 LibraryLoad { path: PathBuf, message: String },
59 #[error("component library does not export the requested entry point: {0}")]
60 MissingEntrypoint(String),
61 #[error("component entrypoint failed with status {0}")]
62 EntrypointFailed(i32),
63 #[error("component returned an invalid descriptor: {0}")]
64 InvalidDescriptor(&'static str),
65 #[error(
66 "component ABI {actual_major}.{actual_minor} is incompatible with host ABI {expected_major}.{expected_minor}"
67 )]
68 IncompatibleAbi {
69 expected_major: u32,
70 expected_minor: u32,
71 actual_major: u32,
72 actual_minor: u32,
73 },
74 #[error("component identity mismatch: expected `{expected}`, got `{actual}`")]
75 IdentityMismatch { expected: String, actual: String },
76 #[error("component contract hash does not match the requested contract")]
77 ContractHashMismatch,
78 #[error("component feature-set hash does not match the requested feature set")]
79 FeatureSetHashMismatch,
80 #[error("component lock contains duplicate entry `{component_id}` for target `{target}`")]
81 DuplicateLockEntry { component_id: String, target: String },
82 #[error("component lock schema version {0} is not supported")]
83 UnsupportedLockSchema(u32),
84 #[error("component lock public key is not a valid Ed25519 public key")]
85 InvalidPublicKey,
86 #[error("component contract table is too small or incorrectly aligned")]
87 InvalidContractTable,
88 #[error("component descriptor does not provide an instance factory")]
89 MissingInstanceFactory,
90 #[error("component descriptor does not provide an instance destructor")]
91 MissingInstanceDestructor,
92 #[error("component instance creation failed with status {status}: {message}")]
93 InstanceCreation { status: i32, message: String },
94 #[error("component instance factory returned a null handle")]
95 NullInstance,
96 #[error("component string is not valid UTF-8")]
97 InvalidUtf8,
98 #[error("component string exceeds the maximum supported length")]
99 StringTooLong,
100 #[error("JSON error: {0}")]
101 Json(#[from] serde_json::Error),
102 #[error("I/O error: {0}")]
103 Io(#[from] std::io::Error),
104}
105
106impl ComponentError {
107 #[must_use]
114 pub fn code(&self) -> &'static str {
115 match self {
116 Self::ArtifactNotFound { .. } => "artifact_not_found",
117 Self::ComponentsNotLocked { .. } => "components_not_locked",
118 Self::BundledComponentNotLoadable { .. } => "bundled_component_not_loadable",
119 Self::ContractNotProvided { .. } => "contract_not_provided",
120 Self::InvalidDigest(_) => "invalid_digest",
121 Self::DigestMismatch { .. } => "digest_mismatch",
122 Self::SizeMismatch { .. } => "size_mismatch",
123 Self::SignatureRequired => "signature_required",
124 Self::InvalidSignatureEncoding => "invalid_signature_encoding",
125 Self::SignatureVerification => "signature_verification_failed",
126 Self::NonCanonicalManifest => "non_canonical_manifest",
127 Self::ManifestDigestMismatch => "manifest_digest_mismatch",
128 Self::ManifestIdentityMismatch => "manifest_identity_mismatch",
129 Self::UnsupportedManifest => "unsupported_manifest",
130 Self::UnsupportedSignatureAlgorithm(_) => "unsupported_signature_algorithm",
131 Self::UnknownSignatureKey(_) => "unknown_signature_key",
132 Self::SignatureKeyMismatch => "signature_key_mismatch",
133 Self::UnsafeArchiveEntry(_) => "unsafe_archive_entry",
134 Self::TooManyArchiveEntries { .. } => "too_many_archive_entries",
135 Self::ExtractedSizeExceeded { .. } => "extracted_size_exceeded",
136 Self::UnsafeLibraryPath(_) => "unsafe_library_path",
137 Self::MissingLibrary(_) => "missing_library",
138 Self::Download { .. } => "download_failed",
139 Self::Offline { .. } => "offline",
140 Self::LibraryLoad { .. } => "library_load_failed",
141 Self::MissingEntrypoint(_) => "missing_entrypoint",
142 Self::EntrypointFailed(_) => "entrypoint_failed",
143 Self::InvalidDescriptor(_) => "invalid_descriptor",
144 Self::IncompatibleAbi { .. } => "incompatible_abi",
145 Self::IdentityMismatch { .. } => "identity_mismatch",
146 Self::ContractHashMismatch => "contract_hash_mismatch",
147 Self::FeatureSetHashMismatch => "feature_set_hash_mismatch",
148 Self::DuplicateLockEntry { .. } => "duplicate_lock_entry",
149 Self::UnsupportedLockSchema(_) => "unsupported_lock_schema",
150 Self::InvalidPublicKey => "invalid_public_key",
151 Self::InvalidContractTable => "invalid_contract_table",
152 Self::MissingInstanceFactory => "missing_instance_factory",
153 Self::MissingInstanceDestructor => "missing_instance_destructor",
154 Self::InstanceCreation { .. } => "instance_creation_failed",
155 Self::NullInstance => "null_instance",
156 Self::InvalidUtf8 => "invalid_utf8",
157 Self::StringTooLong => "string_too_long",
158 Self::Json(_) => "json_error",
159 Self::Io(_) => "io_error",
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn code_is_stable_and_distinct_for_representative_variants() {
170 assert_eq!(
171 ComponentError::Offline {
172 component: "demo".into(),
173 url: "https://example.invalid".into(),
174 }
175 .code(),
176 "offline"
177 );
178 assert_eq!(
179 ComponentError::SignatureVerification.code(),
180 "signature_verification_failed"
181 );
182 assert_eq!(
183 ComponentError::BundledComponentNotLoadable {
184 component: "demo".into()
185 }
186 .code(),
187 "bundled_component_not_loadable"
188 );
189 }
190
191 #[test]
192 fn code_never_appears_inside_its_own_display_message_by_accident() {
193 let error = ComponentError::SignatureVerification;
197 assert!(!error.to_string().contains(error.code()));
198 }
199}