mdzip_core/error.rs
1//! Errors with stable codes.
2//!
3//! Codes follow the MDZip specification's error categories (Section 16.5)
4//! where one exists, and match the other MDZip core libraries and the spec's
5//! shared fixtures. A few codes are specific to this library; they're marked
6//! below.
7
8use std::fmt;
9
10/// A stable, machine-readable error code, such as `ERR_ENTRYPOINT_MISSING`.
11///
12/// Compare codes, not messages: messages can change between releases, codes
13/// don't. [`ErrorCode::as_str`] gives the code as the spec writes it.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum ErrorCode {
17 /// Not a valid ZIP archive, or an entry is corrupt or uses an unsupported
18 /// compression method (Section 4).
19 ZipInvalid,
20 /// An entry is encrypted (Section 4).
21 ZipEncrypted,
22 /// An entry path breaks a rule in Section 5.4 not covered by a more
23 /// specific code below (for example an empty or `.` segment).
24 PathInvalid,
25 /// An entry path is empty.
26 PathEmpty,
27 /// An entry path starts with `/`.
28 PathLeadingSlash,
29 /// An entry path has a `..` segment.
30 PathTraversal,
31 /// An entry path has a reserved character: `\ : * ? " < > |`.
32 PathReservedChar,
33 /// An entry path has a control character (U+0000–U+001F, U+007F).
34 PathControlChar,
35 /// Two entries have the same path after NFC normalization.
36 DuplicatePath,
37 /// A file's path is also the directory of another entry, such as `docs`
38 /// and `docs/intro.md`.
39 PathConflict,
40 /// `manifest.json` is unreadable, or a critical field has the wrong type
41 /// or value (Section 6.4).
42 ManifestInvalid,
43 /// The manifest `entryPoint` isn't a valid entry point path (Section 5.5).
44 EntryPointInvalid,
45 /// The manifest `entryPoint` is valid but matches no entry (Section 5.5).
46 EntryPointMissing,
47 /// No unambiguous entry point could be found (Section 5.5, rule 4).
48 EntryPointUnresolved,
49 /// The archive targets a newer major version of the spec (Section 11).
50 VersionUnsupported,
51 /// The manifest `mode` isn't `document` or `project` (Section 5.7.5).
52 ModeUnsupported,
53 /// A resource limit (entry count, size, compression ratio) was exceeded
54 /// (Section 13.1).
55 LimitExceeded,
56 /// Library-specific: an update would change or break which file the entry
57 /// point resolves to (Section 14.1).
58 EntryPointChanged,
59 /// Library-specific: a requested entry isn't in the archive.
60 NotFound,
61 /// Library-specific: an entry read as text isn't valid UTF-8 (Section 7.1).
62 TextInvalid,
63 /// Library-specific: reading or writing a file on disk failed.
64 Io,
65}
66
67impl ErrorCode {
68 /// The code as the spec writes it, for example `"ERR_ZIP_INVALID"`.
69 pub const fn as_str(self) -> &'static str {
70 match self {
71 ErrorCode::ZipInvalid => "ERR_ZIP_INVALID",
72 ErrorCode::ZipEncrypted => "ERR_ZIP_ENCRYPTED",
73 ErrorCode::PathInvalid => "ERR_PATH_INVALID",
74 ErrorCode::PathEmpty => "ERR_PATH_EMPTY",
75 ErrorCode::PathLeadingSlash => "ERR_PATH_LEADING_SLASH",
76 ErrorCode::PathTraversal => "ERR_PATH_TRAVERSAL",
77 ErrorCode::PathReservedChar => "ERR_PATH_RESERVED_CHAR",
78 ErrorCode::PathControlChar => "ERR_PATH_CONTROL_CHAR",
79 ErrorCode::DuplicatePath => "ERR_DUPLICATE_PATH",
80 ErrorCode::PathConflict => "ERR_PATH_CONFLICT",
81 ErrorCode::ManifestInvalid => "ERR_MANIFEST_INVALID",
82 ErrorCode::EntryPointInvalid => "ERR_ENTRYPOINT_INVALID",
83 ErrorCode::EntryPointMissing => "ERR_ENTRYPOINT_MISSING",
84 ErrorCode::EntryPointUnresolved => "ERR_ENTRYPOINT_UNRESOLVED",
85 ErrorCode::VersionUnsupported => "ERR_VERSION_UNSUPPORTED",
86 ErrorCode::ModeUnsupported => "ERR_MODE_UNSUPPORTED",
87 ErrorCode::LimitExceeded => "ERR_LIMIT_EXCEEDED",
88 ErrorCode::EntryPointChanged => "ERR_ENTRYPOINT_CHANGED",
89 ErrorCode::NotFound => "ERR_NOT_FOUND",
90 ErrorCode::TextInvalid => "ERR_TEXT_INVALID",
91 ErrorCode::Io => "ERR_IO",
92 }
93 }
94}
95
96impl fmt::Display for ErrorCode {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.write_str(self.as_str())
99 }
100}
101
102/// An MDZip error: a stable [`ErrorCode`] and a human-readable message.
103#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct MdzError {
105 code: ErrorCode,
106 message: String,
107}
108
109impl MdzError {
110 /// A new error with `code` and `message`.
111 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
112 MdzError {
113 code,
114 message: message.into(),
115 }
116 }
117
118 /// The stable error code.
119 pub fn code(&self) -> ErrorCode {
120 self.code
121 }
122
123 /// The human-readable message, without the code.
124 pub fn message(&self) -> &str {
125 &self.message
126 }
127}
128
129impl fmt::Display for MdzError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "{}: {}", self.code, self.message)
132 }
133}
134
135impl std::error::Error for MdzError {}
136
137/// `Result` with [`MdzError`].
138pub type Result<T, E = MdzError> = std::result::Result<T, E>;
139
140pub(crate) fn err<T>(code: ErrorCode, message: impl Into<String>) -> Result<T> {
141 Err(MdzError::new(code, message))
142}