1use std::fmt;
10
11include!(concat!(env!("OUT_DIR"), "/code.rs"));
12
13#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Error {
21 code: Code,
22 message: String,
23 position: Option<u32>,
24 detail: Option<String>,
25}
26
27impl Error {
28 pub fn new(code: Code, message: impl Into<String>) -> Error {
40 Error {
41 code,
42 message: yo_alloc::allow(|| message.into()),
43 position: None,
44 detail: None,
45 }
46 }
47
48 pub fn fmt(code: Code, args: fmt::Arguments<'_>) -> Error {
55 Error {
56 code,
57 message: yo_alloc::allow(|| fmt::format(args)),
58 position: None,
59 detail: None,
60 }
61 }
62
63 #[must_use]
69 pub fn at(mut self, position: u32) -> Error {
70 self.position = Some(position);
71 self
72 }
73
74 #[must_use]
76 pub fn with_detail(mut self, detail: impl Into<String>) -> Error {
77 self.detail = Some(yo_alloc::allow(|| detail.into()));
78 self
79 }
80
81 #[inline]
83 pub const fn code(&self) -> Code {
84 self.code
85 }
86
87 #[inline]
89 pub const fn is_retryable(&self) -> bool {
90 self.code.is_retryable()
91 }
92
93 #[inline]
95 pub fn message(&self) -> &str {
96 &self.message
97 }
98
99 #[inline]
101 pub const fn position(&self) -> Option<u32> {
102 self.position
103 }
104
105 #[inline]
107 pub fn detail(&self) -> Option<&str> {
108 self.detail.as_deref()
109 }
110
111 #[inline]
113 pub fn url(&self) -> Option<&'static str> {
114 self.code.url()
115 }
116}
117
118impl fmt::Display for Error {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "{}: {}", self.code.c_name(), self.message)?;
121 if let Some(p) = self.position {
122 write!(f, " (at {p})")?;
123 }
124 if let Some(d) = &self.detail {
125 write!(f, " [{d}]")?;
126 }
127 if let Some(u) = self.code.url() {
128 write!(f, " see {u}")?;
129 }
130 Ok(())
131 }
132}
133
134impl core::error::Error for Error {}
135
136pub type Result<T> = core::result::Result<T, Error>;
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn codes_are_dense_and_stable() {
145 for (i, &c) in Code::ALL.iter().enumerate() {
146 assert_eq!(c.as_u32() as usize, i);
147 assert_eq!(Code::from_u32(c.as_u32()), Some(c));
148 }
149 }
150
151 #[test]
154 fn wire_values_are_frozen() {
155 assert_eq!(Code::Ok.as_u32(), 0);
156 assert_eq!(Code::ShapeMismatch.as_u32(), 1);
157 assert_eq!(Code::Locked.as_u32(), 2);
158 assert_eq!(Code::Busy.as_u32(), 3);
159 assert_eq!(Code::NotFound.as_u32(), 4);
160 assert_eq!(Code::WrongType.as_u32(), 5);
161 assert_eq!(Code::AbiMismatch.as_u32(), 6);
162 assert_eq!(Code::Corrupt.as_u32(), 7);
163 assert_eq!(Code::Full.as_u32(), 8);
164 assert_eq!(Code::Io.as_u32(), 9);
165 assert_eq!(Code::Unsupported.as_u32(), 10);
166 assert_eq!(Code::Invalid.as_u32(), 11);
167 assert_eq!(Code::EpochStalled.as_u32(), 12);
168 assert_eq!(Code::VersionTooNew.as_u32(), 13);
169 }
170
171 #[test]
172 fn an_unknown_code_is_a_value_not_a_panic() {
173 assert_eq!(Code::from_u32(9999), None);
174 }
175
176 #[test]
177 fn retryability_matches_the_model() {
178 assert!(Code::Locked.is_retryable());
179 assert!(Code::Busy.is_retryable());
180 assert!(Code::Io.is_retryable());
181 assert!(Code::EpochStalled.is_retryable());
182 assert!(!Code::ShapeMismatch.is_retryable());
183 assert!(!Code::Corrupt.is_retryable());
184 assert!(!Code::WrongType.is_retryable());
185 }
186
187 #[test]
188 fn display_carries_everything() {
189 let e = Error::new(Code::Invalid, "expected an integer")
190 .at(3)
191 .with_detail("got=abc");
192 let s = e.to_string();
193 assert!(s.contains("YO_ERR_INVALID"), "{s}");
194 assert!(s.contains("expected an integer"), "{s}");
195 assert!(s.contains("at 3"), "{s}");
196 assert!(s.contains("got=abc"), "{s}");
197 }
198
199 #[test]
205 fn building_an_error_is_allowed_where_allocating_is_not() {
206 yo_alloc::enter_no_alloc();
207 let e = Error::fmt(Code::Invalid, format_args!("no such thing: {}", "x"))
208 .with_detail("got=abc");
209 assert_eq!(e.message(), "no such thing: x");
210 assert!(yo_alloc::is_forbidden());
213 yo_alloc::exit_no_alloc();
214 assert!(!yo_alloc::is_forbidden());
215 }
216
217 #[test]
218 fn errors_that_need_a_page_have_one() {
219 for c in [
222 Code::ShapeMismatch,
223 Code::Locked,
224 Code::Busy,
225 Code::WrongType,
226 Code::AbiMismatch,
227 Code::Corrupt,
228 Code::EpochStalled,
229 Code::VersionTooNew,
230 ] {
231 assert!(c.url().is_some(), "{} has no documentation URL", c.c_name());
232 }
233 }
234}