1use std::fmt;
6use std::panic::Location;
7
8use crate::sqlstate::SqlState;
9
10#[derive(Clone, Debug)]
11pub struct Error {
12 state: SqlState,
13 message: String,
14 position: Option<usize>,
15 place: &'static Location<'static>,
17 more: Option<Box<More>>,
19}
20
21#[derive(Clone, Debug, Default, PartialEq, Eq)]
23struct More {
24 detail: Option<String>,
25 hint: Option<String>,
26 context: Option<String>,
27 notice: bool,
29 level: Option<Level>,
31 internal: Option<(String, Option<usize>)>,
33 objects: [Option<String>; 5],
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum Object {
40 Schema,
41 Table,
42 Column,
43 Datatype,
44 Constraint,
45}
46
47impl Object {
48 pub const ALL: [Object; 5] =
50 [Object::Schema, Object::Table, Object::Column, Object::Datatype, Object::Constraint];
51
52 pub fn code(self) -> u8 {
54 match self {
55 Object::Schema => b's',
56 Object::Table => b't',
57 Object::Column => b'c',
58 Object::Datatype => b'd',
59 Object::Constraint => b'n',
60 }
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
66pub enum Level {
67 Debug,
69 Log,
71 Info,
73 Notice,
75 Warning,
77}
78
79impl Level {
80 pub fn name(self) -> &'static str {
82 match self {
83 Level::Debug => "DEBUG",
84 Level::Log => "LOG",
85 Level::Info => "INFO",
86 Level::Notice => "NOTICE",
87 Level::Warning => "WARNING",
88 }
89 }
90}
91
92impl Error {
93 #[track_caller]
95 pub fn new(state: SqlState, message: impl Into<String>) -> Error {
96 Error {
97 state,
98 message: message.into(),
99 position: None,
100 place: Location::caller(),
101 more: None,
102 }
103 }
104
105 #[track_caller]
107 pub fn corrupted(message: impl Into<String>) -> Error {
108 Error::new(SqlState::DATA_CORRUPTED, message)
109 }
110
111 #[track_caller]
113 pub fn internal(message: impl Into<String>) -> Error {
114 Error::new(SqlState::INTERNAL_ERROR, message)
115 }
116
117 #[must_use]
119 pub fn with_detail(mut self, detail: impl Into<String>) -> Error {
120 self.more().detail = Some(detail.into());
121 self
122 }
123
124 #[must_use]
126 pub fn with_hint(mut self, hint: impl Into<String>) -> Error {
127 self.more().hint = Some(hint.into());
128 self
129 }
130
131 #[must_use]
133 pub fn at(mut self, location: usize) -> Error {
134 self.position.get_or_insert(location);
135 self
136 }
137
138 #[must_use]
140 pub fn with_position(mut self, location: Option<usize>) -> Error {
141 self.position = location;
142 self
143 }
144
145 #[must_use]
147 pub fn with_context(mut self, line: impl Into<String>) -> Error {
148 let line = line.into();
149 let more = self.more();
150 more.context = Some(match more.context.take() {
151 Some(context) => format!("{context}\n{line}"),
152 None => line,
153 });
154 self
155 }
156
157 #[must_use]
159 pub fn as_notice(mut self) -> Error {
160 self.more().notice = true;
161 self
162 }
163
164 pub fn is_notice(&self) -> bool {
166 self.more.as_ref().is_some_and(|more| more.notice)
167 }
168
169 #[must_use]
171 pub fn with_level(mut self, level: Level) -> Error {
172 self.more().level = Some(level);
173 self
174 }
175
176 pub fn level(&self) -> Option<Level> {
178 self.more.as_ref()?.level
179 }
180
181 #[must_use]
183 pub fn with_internal(mut self, query: impl Into<String>, position: Option<usize>) -> Error {
184 self.more().internal = Some((query.into(), position));
185 self
186 }
187
188 pub fn internal_query(&self) -> Option<(&str, Option<usize>)> {
190 let (query, position) = self.more.as_ref()?.internal.as_ref()?;
191 Some((query, *position))
192 }
193
194 #[must_use]
196 pub fn with_object(mut self, object: Object, name: impl Into<String>) -> Error {
197 self.more().objects[object as usize] = Some(name.into());
198 self
199 }
200
201 pub fn object(&self, object: Object) -> Option<&str> {
203 self.more.as_ref()?.objects[object as usize].as_deref()
204 }
205
206 fn more(&mut self) -> &mut More {
207 self.more.get_or_insert_with(Box::default)
208 }
209
210 pub fn state(&self) -> SqlState {
212 self.state
213 }
214
215 pub fn message(&self) -> &str {
217 &self.message
218 }
219
220 pub fn detail(&self) -> Option<&str> {
222 self.more.as_ref()?.detail.as_deref()
223 }
224
225 pub fn hint(&self) -> Option<&str> {
227 self.more.as_ref()?.hint.as_deref()
228 }
229
230 pub fn position(&self) -> Option<usize> {
232 self.position
233 }
234
235 pub fn context(&self) -> Option<&str> {
237 self.more.as_ref()?.context.as_deref()
238 }
239
240 pub fn place(&self) -> &'static Location<'static> {
242 self.place
243 }
244}
245
246impl PartialEq for Error {
248 fn eq(&self, other: &Error) -> bool {
249 (self.state, &self.message, self.position, &self.more)
250 == (other.state, &other.message, other.position, &other.more)
251 }
252}
253
254impl Eq for Error {}
255
256impl fmt::Display for Error {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 write!(f, "{}: {}", self.state, self.message)?;
259 if let Some(detail) = self.detail() {
260 write!(f, "\nDETAIL: {detail}")?;
261 }
262 if let Some(hint) = self.hint() {
263 write!(f, "\nHINT: {hint}")?;
264 }
265 Ok(())
266 }
267}
268
269impl std::error::Error for Error {}
270
271pub type Result<T, E = Error> = std::result::Result<T, E>;
273
274#[cfg(test)]
275mod tests {
276 use super::{Error, Object};
277
278 #[test]
279 fn display() {
280 let e = Error::corrupted("bad checksum").with_detail("page 7").with_hint("run rupg verify");
281 assert_eq!(e.to_string(), "XX001: bad checksum\nDETAIL: page 7\nHINT: run rupg verify");
282 assert_eq!((e.position(), e.clone().at(7).at(9).position()), (None, Some(7)));
283 }
284
285 #[test]
286 fn the_place_is_where_the_error_is_made() {
287 let (e, line) = (Error::internal("bad state"), line!());
288 assert_eq!((e.place().file(), e.place().line()), (file!(), line));
289 assert_eq!(e, Error::internal("bad state"));
290 }
291
292 #[test]
293 fn the_names_of_the_object() {
294 let e =
295 Error::internal("x").with_object(Object::Table, "t").with_object(Object::Schema, "s");
296 let names: Vec<_> = Object::ALL.iter().map(|&o| e.object(o)).collect();
297 assert_eq!(names, [Some("s"), Some("t"), None, None, None]);
298 assert_ne!(e, Error::internal("x"));
299 }
300}