Skip to main content

point_formats/
error.rs

1use crate::format::Format;
2use std::fmt;
3use std::path::PathBuf;
4
5/// Crate-wide result alias.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Errors returned by readers, writers, conversion, and format detection.
9#[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}