1#![warn(missing_docs)]
2
3mod category;
18
19#[cfg(feature = "http")]
20mod http;
21
22#[cfg(feature = "json_rpc")]
23mod json_rpc;
24
25use std::fmt::Display;
26
27pub use category::{Category, CategoryExt};
28
29pub type Result<T> = std::result::Result<T, Error>;
31
32pub struct Error {
36 anyhow: anyhow::Error,
37 category: Option<Category>,
38}
39
40impl Error {
41 pub fn into_anyhow(self) -> anyhow::Error {
43 self.anyhow
44 }
45
46 pub fn category(&self) -> Option<&Category> {
48 self.category.as_ref()
49 }
50
51 pub fn context<C>(mut self, context: C) -> Self
53 where
54 C: Display + Send + Sync + 'static,
55 {
56 self.anyhow = self.anyhow.context(context);
57 self
58 }
59
60 #[cfg(feature = "http")]
61 pub fn http_status(&self) -> u16 {
63 http::status_code(self.category.as_ref())
64 }
65
66 #[cfg(feature = "json_rpc")]
67 pub fn json_rpc_status(&self) -> i32 {
69 json_rpc::status_code(self.category.as_ref())
70 }
71}
72
73impl<E> From<E> for Error
74where
75 anyhow::Error: From<E>,
76{
77 fn from(e: E) -> Self {
78 Error {
79 anyhow: anyhow::Error::from(e),
80 category: None,
81 }
82 }
83}
84
85pub trait Context<T, E> {
87 fn context<C>(self, context: C) -> Result<T>
89 where
90 C: Display + Send + Sync + 'static;
91
92 fn with_context<C, F>(self, f: F) -> Result<T>
94 where
95 C: Display + Send + Sync + 'static,
96 F: FnOnce() -> C;
97}
98
99impl<T, E> Context<T, E> for std::result::Result<T, E>
100where
101 E: Into<Error>,
102{
103 fn context<C>(self, context: C) -> Result<T>
104 where
105 C: Display + Send + Sync + 'static,
106 {
107 match self {
108 Ok(t) => Ok(t),
109 Err(e) => Err(e.into().context(context)),
110 }
111 }
112
113 fn with_context<C, F>(self, f: F) -> Result<T>
114 where
115 C: Display + Send + Sync + 'static,
116 F: FnOnce() -> C,
117 {
118 match self {
119 Ok(t) => Ok(t),
120 Err(e) => Err(e.into().context(f())),
121 }
122 }
123}
124
125impl<T> Context<T, std::convert::Infallible> for Option<T> {
126 fn context<C>(self, context: C) -> Result<T>
127 where
128 C: Display + Send + Sync + 'static,
129 {
130 match self {
131 Some(t) => Ok(t),
132 None => Err(anyhow::anyhow!(context.to_string()).into()),
133 }
134 }
135
136 fn with_context<C, F>(self, f: F) -> Result<T>
137 where
138 C: Display + Send + Sync + 'static,
139 F: FnOnce() -> C,
140 {
141 match self {
142 Some(t) => Ok(t),
143 None => Err(anyhow::anyhow!(f().to_string()).into()),
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn returns_with_question_mark() {
154 fn _api() -> super::Result<()> {
155 "foo".parse::<usize>()?;
156 Ok(())
157 }
158 }
159
160 #[test]
161 fn attaches_cause() {
162 let err = "foo".parse::<usize>().bad_request();
163
164 assert_eq!(err.unwrap_err().category(), Some(&Category::BadRequest));
165 }
166
167 #[test]
168 fn can_attach_context() {
169 let _ = "foo".parse::<usize>().bad_request().context("Some context");
170 }
171}