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 contract entry carries declared-command bytes no release ever
69    /// minted: the two archive forms mixed in one contract, one command
70    /// carrying both forms' fields, or a prior-form command that does not
71    /// decode as the prior wire shape.
72    #[error("contract.json declared-command entry rejected: {detail}")]
73    ContractPriorFormMalformed {
74        /// What was found, naming the queue and command or action.
75        detail: String,
76    },
77
78    /// The manifest declares a format version this crate does not support.
79    #[error("unknown .aion format_version {found}")]
80    UnknownFormatVersion {
81        /// Unsupported format version found in the manifest.
82        found: u32,
83    },
84
85    /// The manifest entry module is not present in the beam set.
86    #[error("missing entry module `{module}` in beam set")]
87    MissingEntryModule {
88        /// Logical entry module named by the manifest.
89        module: String,
90    },
91
92    /// The manifest version does not match the hash recomputed from beams.
93    #[error("package integrity mismatch: expected version `{expected}`, computed `{computed}`")]
94    IntegrityMismatch {
95        /// Version claimed by the manifest.
96        expected: String,
97        /// Version recomputed from package beams.
98        computed: String,
99    },
100
101    /// A beam archive entry is malformed or ambiguous.
102    #[error("malformed beam entry `{entry}`")]
103    MalformedBeamEntry {
104        /// Archive entry or logical module name that failed validation.
105        entry: String,
106    },
107
108    /// An `awl/` archive entry is malformed, duplicated, or names a family
109    /// this format does not define.
110    #[error("malformed AWL source entry `{entry}`")]
111    MalformedAwlEntry {
112        /// Archive entry or relative path that failed validation.
113        entry: String,
114    },
115
116    /// The archived AWL document is not UTF-8 text.
117    #[error("archived AWL document `{entry}` is not valid UTF-8: {source}")]
118    AwlDocumentNotUtf8 {
119        /// Archive entry that failed decoding.
120        entry: String,
121        /// Decoding failure reported by the standard library.
122        source: std::string::FromUtf8Error,
123    },
124
125    /// The archive carries imported schema files with no document to own them,
126    /// so the provenance it claims is incomplete.
127    #[error("archive carries `{AWL_SCHEMA_PREFIX}` entries but no `{AWL_DOCUMENT_PREFIX}` entry")]
128    MissingAwlDocument,
129
130    /// The archive's entries inflate past the caller's extraction budget.
131    #[error(
132        "archive contents inflate past the extraction limit of {limit} bytes; refusing to extract further"
133    )]
134    InflatedSizeExceeded {
135        /// The caller-configured inflate ceiling in bytes.
136        limit: u64,
137    },
138}
139
140#[cfg(test)]
141mod tests {
142    use super::PackageError;
143
144    fn assert_send_sync<T: Send + Sync>() {}
145
146    #[test]
147    fn package_error_is_send_and_sync() {
148        assert_send_sync::<PackageError>();
149    }
150
151    #[test]
152    fn display_messages_name_the_failed_condition() {
153        assert_eq!(
154            PackageError::MissingManifest.to_string(),
155            "missing required manifest.json entry"
156        );
157        assert_eq!(
158            PackageError::ArchiveWriteIo {
159                source: std::io::Error::other("disk full"),
160            }
161            .to_string(),
162            "failed to write .aion archive bytes: disk full"
163        );
164        assert_eq!(
165            PackageError::UnknownFormatVersion { found: 99 }.to_string(),
166            "unknown .aion format_version 99"
167        );
168        assert_eq!(
169            PackageError::MissingEntryModule {
170                module: "workflow/main".to_owned(),
171            }
172            .to_string(),
173            "missing entry module `workflow/main` in beam set"
174        );
175        assert_eq!(
176            PackageError::IntegrityMismatch {
177                expected: "expected".to_owned(),
178                computed: "computed".to_owned(),
179            }
180            .to_string(),
181            "package integrity mismatch: expected version `expected`, computed `computed`"
182        );
183        assert_eq!(
184            PackageError::MalformedBeamEntry {
185                entry: "beam/workflow.beam".to_owned(),
186            }
187            .to_string(),
188            "malformed beam entry `beam/workflow.beam`"
189        );
190        assert_eq!(
191            PackageError::InflatedSizeExceeded { limit: 1024 }.to_string(),
192            "archive contents inflate past the extraction limit of 1024 bytes; refusing to extract further"
193        );
194    }
195}