1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum Nd2Error {
5 #[error("file error: {source}")]
6 File { source: FileError },
7
8 #[error("input error: {source}")]
9 Input { source: InputError },
10
11 #[error("internal error: {source}")]
12 Internal { source: InternalError },
13
14 #[error("unsupported: {source}")]
15 Unsupported { source: UnsupportedError },
16}
17
18#[derive(Error, Debug)]
19pub enum FileError {
20 #[error("IO error: {0}")]
21 Io(#[from] std::io::Error),
22
23 #[error("Invalid ND2 file: {context}")]
24 InvalidFormat { context: String },
25
26 #[error("Invalid magic number: expected 0x{expected:08X}, got 0x{actual:08X}")]
27 InvalidMagic { expected: u32, actual: u32 },
28
29 #[error("Corrupt chunk header at position {position}")]
30 CorruptChunkHeader { position: u64 },
31
32 #[error("Chunk '{name}' not found in chunkmap")]
33 ChunkNotFound { name: String },
34
35 #[error("Invalid chunkmap signature")]
36 InvalidChunkmapSignature,
37
38 #[error("Chunkmap error: {context}")]
39 ChunkmapParse { context: String },
40
41 #[error("CLX parsing error: {context}")]
42 ClxParse { context: String },
43
44 #[error("Decompression error: {context}")]
45 Decompression { context: String },
46
47 #[error("UTF-16 decoding error: {context}")]
48 Utf16Decode { context: String },
49
50 #[error("Metadata parse error: {context}")]
51 MetadataParse { context: String },
52}
53
54#[derive(Error, Debug)]
55pub enum InputError {
56 #[error("Missing required dimension '{dimension}'")]
57 MissingDimension { dimension: String },
58
59 #[error("{field} index out of range: got {index}, max {max}")]
60 OutOfRange {
61 field: String,
62 index: usize,
63 max: usize,
64 },
65
66 #[error("Invalid input for {field}: {detail}")]
67 InvalidArgument { field: String, detail: String },
68
69 #[error("Incompatible parameters: expected {expected}, provided {provided}")]
70 IncompatibleParams { expected: String, provided: String },
71}
72
73#[derive(Error, Debug)]
74pub enum InternalError {
75 #[error("Arithmetic overflow during {operation}")]
76 Overflow { operation: String },
77
78 #[error("Internal invariant violation: {detail}")]
79 InvariantViolation { detail: String },
80}
81
82#[derive(Error, Debug)]
83pub enum UnsupportedError {
84 #[error("Unsupported ND2 file version: {major}.{minor}")]
85 Version { major: u32, minor: u32 },
86
87 #[error("Unsupported CLX data type: {type_code}")]
88 ClxType { type_code: u8 },
89}
90
91#[derive(Copy, Clone, Debug, PartialEq, Eq)]
92pub enum ErrorSource {
93 File,
94 Input,
95 Internal,
96 Unsupported,
97}
98
99impl Nd2Error {
100 pub fn source(&self) -> ErrorSource {
101 match self {
102 Self::File { .. } => ErrorSource::File,
103 Self::Input { .. } => ErrorSource::Input,
104 Self::Internal { .. } => ErrorSource::Internal,
105 Self::Unsupported { .. } => ErrorSource::Unsupported,
106 }
107 }
108
109 pub fn is_file(&self) -> bool {
110 matches!(self, Self::File { .. })
111 }
112
113 pub fn is_input(&self) -> bool {
114 matches!(self, Self::Input { .. })
115 }
116
117 pub fn is_internal(&self) -> bool {
118 matches!(self, Self::Internal { .. })
119 }
120
121 pub fn is_unsupported(&self) -> bool {
122 matches!(self, Self::Unsupported { .. })
123 }
124
125 pub fn file_invalid_format(context: impl Into<String>) -> Self {
126 Self::File {
127 source: FileError::InvalidFormat {
128 context: context.into(),
129 },
130 }
131 }
132
133 pub fn file_chunkmap(context: impl Into<String>) -> Self {
134 Self::File {
135 source: FileError::ChunkmapParse {
136 context: context.into(),
137 },
138 }
139 }
140
141 pub fn file_metadata(context: impl Into<String>) -> Self {
142 Self::File {
143 source: FileError::MetadataParse {
144 context: context.into(),
145 },
146 }
147 }
148
149 pub fn file_invalid_magic(expected: u32, actual: u32) -> Self {
150 Self::File {
151 source: FileError::InvalidMagic { expected, actual },
152 }
153 }
154
155 pub fn file_chunk_not_found(name: impl Into<String>) -> Self {
156 Self::File {
157 source: FileError::ChunkNotFound { name: name.into() },
158 }
159 }
160
161 pub fn input_out_of_range(field: impl Into<String>, index: usize, max: usize) -> Self {
162 Self::Input {
163 source: InputError::OutOfRange {
164 field: field.into(),
165 index,
166 max,
167 },
168 }
169 }
170
171 pub fn input_missing_dim(dimension: impl Into<String>) -> Self {
172 Self::Input {
173 source: InputError::MissingDimension {
174 dimension: dimension.into(),
175 },
176 }
177 }
178
179 pub fn input_argument(field: impl Into<String>, detail: impl Into<String>) -> Self {
180 Self::Input {
181 source: InputError::InvalidArgument {
182 field: field.into(),
183 detail: detail.into(),
184 },
185 }
186 }
187
188 pub fn internal_overflow(operation: impl Into<String>) -> Self {
189 Self::Internal {
190 source: InternalError::Overflow {
191 operation: operation.into(),
192 },
193 }
194 }
195
196 pub fn unsupported_version(major: u32, minor: u32) -> Self {
197 Self::Unsupported {
198 source: UnsupportedError::Version { major, minor },
199 }
200 }
201
202 pub fn unsupported_clx_type(type_code: u8) -> Self {
203 Self::Unsupported {
204 source: UnsupportedError::ClxType { type_code },
205 }
206 }
207}
208
209impl From<std::io::Error> for Nd2Error {
210 fn from(value: std::io::Error) -> Self {
211 Self::File {
212 source: FileError::Io(value),
213 }
214 }
215}
216
217pub type Result<T> = std::result::Result<T, Nd2Error>;