1use std::fmt::Display;
4use std::io;
5
6use ipnetwork::IpNetworkError;
7use serde::de;
8use thiserror::Error;
9
10#[derive(Error, Debug)]
12#[non_exhaustive]
13pub enum MaxMindDbError {
14 #[error("{}", format_invalid_database(.message, .offset))]
16 InvalidDatabase {
17 message: String,
19 offset: Option<usize>,
21 },
22
23 #[error("i/o error: {0}")]
25 Io(
26 #[from]
27 #[source]
28 io::Error,
29 ),
30
31 #[cfg(feature = "mmap")]
33 #[error("memory map error: {0}")]
34 Mmap(#[source] io::Error),
35
36 #[error("{}", format_decoding_error(.message, .offset, .path.as_deref()))]
38 Decoding {
39 message: String,
41 offset: Option<usize>,
43 path: Option<String>,
45 },
46
47 #[error("invalid network: {0}")]
49 InvalidNetwork(
50 #[from]
51 #[source]
52 IpNetworkError,
53 ),
54
55 #[error("invalid input: {message}")]
57 InvalidInput {
58 message: String,
60 },
61}
62
63fn format_invalid_database(message: &str, offset: &Option<usize>) -> String {
64 match offset {
65 Some(off) => format!("invalid database at offset {off}: {message}"),
66 None => format!("invalid database: {message}"),
67 }
68}
69
70fn format_decoding_error(message: &str, offset: &Option<usize>, path: Option<&str>) -> String {
71 match (offset, path) {
72 (Some(off), Some(p)) => format!("decoding error at offset {off} (path: {p}): {message}"),
73 (Some(off), None) => format!("decoding error at offset {off}: {message}"),
74 (None, Some(p)) => format!("decoding error (path: {p}): {message}"),
75 (None, None) => format!("decoding error: {message}"),
76 }
77}
78
79impl MaxMindDbError {
80 pub fn invalid_database(message: impl Into<String>) -> Self {
82 MaxMindDbError::InvalidDatabase {
83 message: message.into(),
84 offset: None,
85 }
86 }
87
88 pub fn invalid_database_at(message: impl Into<String>, offset: usize) -> Self {
90 MaxMindDbError::InvalidDatabase {
91 message: message.into(),
92 offset: Some(offset),
93 }
94 }
95
96 pub fn decoding(message: impl Into<String>) -> Self {
98 MaxMindDbError::Decoding {
99 message: message.into(),
100 offset: None,
101 path: None,
102 }
103 }
104
105 pub fn decoding_at(message: impl Into<String>, offset: usize) -> Self {
107 MaxMindDbError::Decoding {
108 message: message.into(),
109 offset: Some(offset),
110 path: None,
111 }
112 }
113
114 pub fn decoding_at_path(
116 message: impl Into<String>,
117 offset: usize,
118 path: impl Into<String>,
119 ) -> Self {
120 MaxMindDbError::Decoding {
121 message: message.into(),
122 offset: Some(offset),
123 path: Some(path.into()),
124 }
125 }
126
127 pub fn invalid_input(message: impl Into<String>) -> Self {
129 MaxMindDbError::InvalidInput {
130 message: message.into(),
131 }
132 }
133}
134
135impl de::Error for MaxMindDbError {
136 fn custom<T: Display>(msg: T) -> Self {
137 MaxMindDbError::decoding(msg.to_string())
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::io::{Error, ErrorKind};
145
146 #[test]
147 fn test_error_display() {
148 assert_eq!(
150 format!(
151 "{}",
152 MaxMindDbError::invalid_database("something went wrong")
153 ),
154 "invalid database: something went wrong".to_owned(),
155 );
156 assert_eq!(
158 format!(
159 "{}",
160 MaxMindDbError::invalid_database_at("something went wrong", 42)
161 ),
162 "invalid database at offset 42: something went wrong".to_owned(),
163 );
164 let io_err = Error::new(ErrorKind::NotFound, "file not found");
165 assert_eq!(
166 format!("{}", MaxMindDbError::from(io_err)),
167 "i/o error: file not found".to_owned(),
168 );
169
170 #[cfg(feature = "mmap")]
171 {
172 let mmap_io_err = Error::new(ErrorKind::PermissionDenied, "mmap failed");
173 assert_eq!(
174 format!("{}", MaxMindDbError::Mmap(mmap_io_err)),
175 "memory map error: mmap failed".to_owned(),
176 );
177 }
178
179 assert_eq!(
181 format!("{}", MaxMindDbError::decoding("unexpected type")),
182 "decoding error: unexpected type".to_owned(),
183 );
184 assert_eq!(
186 format!("{}", MaxMindDbError::decoding_at("unexpected type", 100)),
187 "decoding error at offset 100: unexpected type".to_owned(),
188 );
189 assert_eq!(
191 format!(
192 "{}",
193 MaxMindDbError::decoding_at_path("unexpected type", 100, "/city/names/en")
194 ),
195 "decoding error at offset 100 (path: /city/names/en): unexpected type".to_owned(),
196 );
197
198 let net_err = IpNetworkError::InvalidPrefix;
199 assert_eq!(
200 format!("{}", MaxMindDbError::from(net_err)),
201 "invalid network: invalid prefix".to_owned(),
202 );
203
204 assert_eq!(
206 format!("{}", MaxMindDbError::invalid_input("bad address")),
207 "invalid input: bad address".to_owned(),
208 );
209 }
210}