1use crate::format::Format;
2use std::fmt;
3use std::path::PathBuf;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug)]
10pub enum Error {
11 Io(std::io::Error),
12 UnknownFormat {
13 path: PathBuf,
14 },
15 UnsupportedFormat {
16 format: Format,
17 operation: &'static str,
18 hint: &'static str,
19 },
20 Parse {
21 format: Format,
22 line: Option<usize>,
23 message: String,
24 },
25 InvalidData {
26 message: String,
27 },
28 LossyConversionBlocked {
29 from: &'static str,
30 to: Format,
31 reason: String,
32 },
33}
34
35impl Error {
36 pub(crate) fn parse(
37 format: Format,
38 line: impl Into<Option<usize>>,
39 message: impl Into<String>,
40 ) -> Self {
41 Self::Parse {
42 format,
43 line: line.into(),
44 message: message.into(),
45 }
46 }
47
48 pub(crate) fn invalid(message: impl Into<String>) -> Self {
49 Self::InvalidData {
50 message: message.into(),
51 }
52 }
53
54 pub(crate) fn unsupported(format: Format, operation: &'static str, hint: &'static str) -> Self {
55 Self::UnsupportedFormat {
56 format,
57 operation,
58 hint,
59 }
60 }
61}
62
63impl From<std::io::Error> for Error {
64 fn from(error: std::io::Error) -> Self {
65 Self::Io(error)
66 }
67}
68
69impl fmt::Display for Error {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::Io(error) => write!(f, "I/O error: {error}"),
73 Self::UnknownFormat { path } => write!(
74 f,
75 "could not infer file format from path '{}'",
76 path.display()
77 ),
78 Self::UnsupportedFormat {
79 format,
80 operation,
81 hint,
82 } => write!(f, "{operation} is not supported for {format}: {hint}"),
83 Self::Parse {
84 format,
85 line,
86 message,
87 } => match line {
88 Some(line) => write!(f, "failed to parse {format} at line {line}: {message}"),
89 None => write!(f, "failed to parse {format}: {message}"),
90 },
91 Self::InvalidData { message } => write!(f, "invalid data: {message}"),
92 Self::LossyConversionBlocked { from, to, reason } => write!(
93 f,
94 "refusing lossy conversion from {from} to {to}: {reason}; set allow_lossy=true to permit it"
95 ),
96 }
97 }
98}
99
100impl std::error::Error for Error {}
101
102#[cfg(feature = "gpkg")]
103impl From<rusqlite::Error> for Error {
104 fn from(error: rusqlite::Error) -> Self {
105 Self::invalid(format!("SQLite error: {error}"))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_error_variants() {
115 let err_io = Error::from(std::io::Error::new(
116 std::io::ErrorKind::NotFound,
117 "file not found",
118 ));
119 assert!(err_io.to_string().contains("file not found"));
120
121 let err_unknown = Error::UnknownFormat {
122 path: PathBuf::from("test.unknown"),
123 };
124 assert!(err_unknown
125 .to_string()
126 .contains("could not infer file format"));
127
128 let err_unsupported = Error::unsupported(Format::NetCdf, "read", "use netcdf adapter");
129 assert!(err_unsupported
130 .to_string()
131 .contains("read is not supported for netcdf: use netcdf adapter"));
132
133 let err_parse_line = Error::parse(Format::Ply, 15, "bad keyword");
134 assert!(err_parse_line
135 .to_string()
136 .contains("failed to parse ply at line 15: bad keyword"));
137
138 let err_parse_no_line = Error::parse(Format::Ply, None, "bad header");
139 assert!(err_parse_no_line
140 .to_string()
141 .contains("failed to parse ply: bad header"));
142
143 let err_invalid = Error::invalid("corrupt");
144 assert!(err_invalid.to_string().contains("invalid data: corrupt"));
145
146 let err_lossy = Error::LossyConversionBlocked {
147 from: "mesh",
148 to: Format::Xyz,
149 reason: "discarding faces".to_string(),
150 };
151 assert!(err_lossy
152 .to_string()
153 .contains("refusing lossy conversion from mesh to xyz: discarding faces"));
154 }
155
156 #[cfg(feature = "gpkg")]
157 #[test]
158 fn test_sqlite_error_conversion() {
159 let sql_err = rusqlite::Error::QueryReturnedNoRows;
160 let err = Error::from(sql_err);
161 assert!(err.to_string().contains("Query returned no rows"));
162 }
163}