summaryrefslogtreecommitdiffstats
path: root/crates/rebel-common/src/error.rs
blob: ba25af474cde1b68420d0384106a20aede6442aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Serializable errors with context

use std::{error::Error as _, fmt::Display, io, result};

use serde::{Deserialize, Serialize};

pub trait Contextualizable: Sized {
	type Output;

	fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output;

	fn context<C: Display>(self, c: C) -> Self::Output {
		self.with_context(|| c)
	}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum ErrorCause {
	Code(i32),
	String(String),
}

impl Display for ErrorCause {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			ErrorCause::Code(code) => io::Error::from_raw_os_error(*code).fmt(f),
			ErrorCause::String(string) => f.write_str(string),
		}
	}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Error {
	pub cause: ErrorCause,
	pub context: Vec<String>,
}

impl Error {
	pub fn new<D: Display>(cause: D) -> Self {
		Error {
			cause: ErrorCause::String(cause.to_string()),
			context: Vec::new(),
		}
	}

	pub fn from_io(err: &io::Error) -> Self {
		if let Some(source) = err
			.source()
			.and_then(|source| source.downcast_ref::<io::Error>())
		{
			return Error::from_io(source).context(err.to_string());
		}

		let cause = match err.raw_os_error() {
			Some(code) => ErrorCause::Code(code),
			None => ErrorCause::String(err.to_string()),
		};
		Error {
			cause,
			context: vec![],
		}
	}
}

impl Display for Error {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.write_str("Error: ")?;

		let mut it = self.context.iter().rev();
		if let Some(ctx) = it.next() {
			write!(f, "{}\n\nCaused by:\n    ", ctx)?;

			for ctx in it {
				write!(f, "{}\n    ", ctx)?;
			}
		}

		self.cause.fmt(f)
	}
}

impl Contextualizable for Error {
	type Output = Error;
	fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output {
		let Error { cause, mut context } = self;
		context.push(f().to_string());
		Error { cause, context }
	}
}

impl<E> From<E> for Error
where
	E: Into<io::Error>,
{
	fn from(err: E) -> Self {
		Error::from_io(&err.into())
	}
}

pub type Result<T> = result::Result<T, Error>;

impl<T, E> Contextualizable for result::Result<T, E>
where
	E: Into<Error>,
{
	type Output = Result<T>;

	fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output {
		self.map_err(|err| err.into().with_context(f))
	}
}

impl<T> Contextualizable for Option<T> {
	type Output = Result<T>;

	fn with_context<C: Display, F: FnOnce() -> C>(self, f: F) -> Self::Output {
		self.ok_or_else(|| Error::new(f()))
	}
}