Skip to main content

aion_package/
error.rs

1//! Error taxonomy for malformed `.aion` packages.
2
3use crate::awl::{AWL_DOCUMENT_PREFIX, AWL_SCHEMA_PREFIX};
4
5/// Errors produced while validating or loading a `.aion` package.
6#[derive(thiserror::Error, Debug)]
7pub enum PackageError {
8    /// The archive could not be read as a ZIP container.
9    #[error("failed to read .aion ZIP archive: {0}")]
10    ArchiveRead(#[from] zip::result::ZipError),
11
12    /// The archive does not contain the required root manifest.
13    #[error("missing required manifest.json entry")]
14    MissingManifest,
15
16    /// A new package builder has no contract record to bind into `.v4` identity.
17    #[error("missing required contract record for .v4 package identity")]
18    MissingContractRecord,
19
20    /// A module uses a namespace owned by the engine's native NIF layer.
21    #[error(
22        "module `{module}` uses an engine-reserved namespace and must not ship as package bytecode"
23    )]
24    ReservedModuleName {
25        /// The offending logical module name.
26        module: String,
27    },
28
29    /// The archive could not be written as a ZIP container.
30    #[error("failed to write .aion ZIP archive: {0}")]
31    ArchiveWrite(zip::result::ZipError),
32
33    /// The archive target could not be written to the filesystem or memory buffer.
34    #[error("failed to write .aion archive bytes: {source}")]
35    ArchiveWriteIo {
36        /// I/O failure reported by the write target.
37        source: std::io::Error,
38    },
39
40    /// The manifest entry is present but is not valid manifest JSON.
41    #[error("failed to parse manifest.json: {source}")]
42    ManifestParse {
43        /// JSON parsing failure reported by `serde_json`.
44        source: serde_json::Error,
45    },
46
47    /// The manifest could not be serialised for writing into the archive.
48    #[error("failed to serialise manifest.json: {source}")]
49    ManifestSerialise {
50        /// JSON serialisation failure reported by `serde_json`.
51        source: serde_json::Error,
52    },
53
54    /// The durable contract record is present but is not valid contract JSON.
55    #[error("failed to parse contract.json: {source}")]
56    ContractParse {
57        /// JSON parsing failure reported by `serde_json`.
58        source: serde_json::Error,
59    },
60
61    /// The durable contract record could not be serialised.
62    #[error("failed to serialise contract.json: {source}")]
63    ContractSerialise {
64        /// JSON serialisation failure reported by `serde_json`.
65        source: serde_json::Error,
66    },
67
68    /// The manifest declares a format version this crate does not support.
69    #[error("unknown .aion format_version {found}")]
70    UnknownFormatVersion {
71        /// Unsupported format version found in the manifest.
72        found: u32,
73    },
74
75    /// The manifest entry module is not present in the beam set.
76    #[error("missing entry module `{module}` in beam set")]
77    MissingEntryModule {
78        /// Logical entry module named by the manifest.
79        module: String,
80    },
81
82    /// The manifest version does not match the hash recomputed from beams.
83    #[error("package integrity mismatch: expected version `{expected}`, computed `{computed}`")]
84    IntegrityMismatch {
85        /// Version claimed by the manifest.
86        expected: String,
87        /// Version recomputed from package beams.
88        computed: String,
89    },
90
91    /// A beam archive entry is malformed or ambiguous.
92    #[error("malformed beam entry `{entry}`")]
93    MalformedBeamEntry {
94        /// Archive entry or logical module name that failed validation.
95        entry: String,
96    },
97
98    /// An `awl/` archive entry is malformed, duplicated, or names a family
99    /// this format does not define.
100    #[error("malformed AWL source entry `{entry}`")]
101    MalformedAwlEntry {
102        /// Archive entry or relative path that failed validation.
103        entry: String,
104    },
105
106    /// The archived AWL document is not UTF-8 text.
107    #[error("archived AWL document `{entry}` is not valid UTF-8: {source}")]
108    AwlDocumentNotUtf8 {
109        /// Archive entry that failed decoding.
110        entry: String,
111        /// Decoding failure reported by the standard library.
112        source: std::string::FromUtf8Error,
113    },
114
115    /// The archive carries imported schema files with no document to own them,
116    /// so the provenance it claims is incomplete.
117    #[error("archive carries `{AWL_SCHEMA_PREFIX}` entries but no `{AWL_DOCUMENT_PREFIX}` entry")]
118    MissingAwlDocument,
119
120    /// The archive's entries inflate past the caller's extraction budget.
121    #[error(
122        "archive contents inflate past the extraction limit of {limit} bytes; refusing to extract further"
123    )]
124    InflatedSizeExceeded {
125        /// The caller-configured inflate ceiling in bytes.
126        limit: u64,
127    },
128}
129
130#[cfg(test)]
131mod tests {
132    use super::PackageError;
133
134    fn assert_send_sync<T: Send + Sync>() {}
135
136    #[test]
137    fn package_error_is_send_and_sync() {
138        assert_send_sync::<PackageError>();
139    }
140
141    #[test]
142    fn display_messages_name_the_failed_condition() {
143        assert_eq!(
144            PackageError::MissingManifest.to_string(),
145            "missing required manifest.json entry"
146        );
147        assert_eq!(
148            PackageError::ArchiveWriteIo {
149                source: std::io::Error::other("disk full"),
150            }
151            .to_string(),
152            "failed to write .aion archive bytes: disk full"
153        );
154        assert_eq!(
155            PackageError::UnknownFormatVersion { found: 99 }.to_string(),
156            "unknown .aion format_version 99"
157        );
158        assert_eq!(
159            PackageError::MissingEntryModule {
160                module: "workflow/main".to_owned(),
161            }
162            .to_string(),
163            "missing entry module `workflow/main` in beam set"
164        );
165        assert_eq!(
166            PackageError::IntegrityMismatch {
167                expected: "expected".to_owned(),
168                computed: "computed".to_owned(),
169            }
170            .to_string(),
171            "package integrity mismatch: expected version `expected`, computed `computed`"
172        );
173        assert_eq!(
174            PackageError::MalformedBeamEntry {
175                entry: "beam/workflow.beam".to_owned(),
176            }
177            .to_string(),
178            "malformed beam entry `beam/workflow.beam`"
179        );
180        assert_eq!(
181            PackageError::InflatedSizeExceeded { limit: 1024 }.to_string(),
182            "archive contents inflate past the extraction limit of 1024 bytes; refusing to extract further"
183        );
184    }
185}