1use core::fmt;
4
5use ecow::EcoString;
6use serde::{Deserialize, Serialize};
7#[cfg(feature = "typst")]
8use typst::diag::SourceDiagnostic;
9
10use lsp_types::Range as LspRange;
11
12#[derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr, Debug, Clone)]
14#[repr(u8)]
15pub enum DiagSeverity {
16 Error = 1,
18 Warning = 2,
20 Information = 3,
22 Hint = 4,
24}
25
26impl fmt::Display for DiagSeverity {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 DiagSeverity::Error => write!(f, "error"),
30 DiagSeverity::Warning => write!(f, "warning"),
31 DiagSeverity::Information => write!(f, "information"),
32 DiagSeverity::Hint => write!(f, "hint"),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DiagMessage {
42 pub package: String,
44 pub path: String,
46 pub message: EcoString,
48 pub severity: DiagSeverity,
50 pub range: Option<LspRange>,
52}
53
54impl DiagMessage {}
55
56#[derive(Debug, Clone)]
58#[non_exhaustive]
59pub enum ErrKind {
60 None,
62 Msg(EcoString),
64 #[cfg(feature = "typst")]
66 RawDiag(ecow::EcoVec<SourceDiagnostic>),
67 Diag(Box<DiagMessage>),
69 Inner(Error),
71}
72
73pub trait ErrKindExt {
75 fn to_error_kind(self) -> ErrKind;
77}
78
79impl ErrKindExt for ErrKind {
80 fn to_error_kind(self) -> Self {
81 self
82 }
83}
84
85impl ErrKindExt for std::io::Error {
86 fn to_error_kind(self) -> ErrKind {
87 ErrKind::Msg(self.to_string().into())
88 }
89}
90
91impl ErrKindExt for std::str::Utf8Error {
92 fn to_error_kind(self) -> ErrKind {
93 ErrKind::Msg(self.to_string().into())
94 }
95}
96
97impl ErrKindExt for String {
98 fn to_error_kind(self) -> ErrKind {
99 ErrKind::Msg(self.into())
100 }
101}
102
103impl ErrKindExt for &str {
104 fn to_error_kind(self) -> ErrKind {
105 ErrKind::Msg(self.into())
106 }
107}
108
109impl ErrKindExt for &String {
110 fn to_error_kind(self) -> ErrKind {
111 ErrKind::Msg(self.into())
112 }
113}
114
115impl ErrKindExt for EcoString {
116 fn to_error_kind(self) -> ErrKind {
117 ErrKind::Msg(self)
118 }
119}
120
121impl ErrKindExt for &dyn std::fmt::Display {
122 fn to_error_kind(self) -> ErrKind {
123 ErrKind::Msg(self.to_string().into())
124 }
125}
126
127impl ErrKindExt for serde_json::Error {
128 fn to_error_kind(self) -> ErrKind {
129 ErrKind::Msg(self.to_string().into())
130 }
131}
132
133impl ErrKindExt for anyhow::Error {
134 fn to_error_kind(self) -> ErrKind {
135 ErrKind::Msg(self.to_string().into())
136 }
137}
138
139impl ErrKindExt for Error {
140 fn to_error_kind(self) -> ErrKind {
141 ErrKind::Msg(self.to_string().into())
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct ErrorImpl {
148 loc: &'static str,
150 kind: ErrKind,
152 args: Option<Box<[(&'static str, String)]>>,
154}
155
156#[derive(Debug, Clone)]
158pub struct Error {
159 err: Box<ErrorImpl>,
163}
164
165impl Error {
166 pub fn new(
168 loc: &'static str,
169 kind: ErrKind,
170 args: Option<Box<[(&'static str, String)]>>,
171 ) -> Self {
172 Self {
173 err: Box::new(ErrorImpl { loc, kind, args }),
174 }
175 }
176
177 pub fn loc(&self) -> &'static str {
179 self.err.loc
180 }
181
182 pub fn kind(&self) -> &ErrKind {
184 &self.err.kind
185 }
186
187 pub fn arguments(&self) -> &[(&'static str, String)] {
189 self.err.args.as_deref().unwrap_or_default()
190 }
191}
192
193impl fmt::Display for Error {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 let err = &self.err;
196
197 if err.loc.is_empty() {
198 match &err.kind {
199 ErrKind::Msg(msg) => write!(f, "{msg} with {:?}", err.args),
200 #[cfg(feature = "typst")]
201 ErrKind::RawDiag(diag) => {
202 write!(f, "{diag:?} with {:?}", err.args)
203 }
204 ErrKind::Diag(diag) => {
205 write!(f, "{} with {:?}", diag.message, err.args)
206 }
207 ErrKind::Inner(e) => write!(f, "{e} with {:?}", err.args),
208 ErrKind::None => write!(f, "error with {:?}", err.args),
209 }
210 } else {
211 match &err.kind {
212 ErrKind::Msg(msg) => write!(f, "{}: {} with {:?}", err.loc, msg, err.args),
213 #[cfg(feature = "typst")]
214 ErrKind::RawDiag(diag) => {
215 write!(f, "{}: {diag:?} with {:?}", err.loc, err.args)
216 }
217 ErrKind::Diag(diag) => {
218 write!(f, "{}: {} with {:?}", err.loc, diag.message, err.args)
219 }
220 ErrKind::Inner(e) => write!(f, "{}: {} with {:?}", err.loc, e, err.args),
221 ErrKind::None => write!(f, "{}: with {:?}", err.loc, err.args),
222 }
223 }
224 }
225}
226
227impl From<anyhow::Error> for Error {
228 fn from(e: anyhow::Error) -> Self {
229 Error::new("", e.to_string().to_error_kind(), None)
230 }
231}
232
233#[cfg(feature = "typst")]
234impl From<ecow::EcoVec<SourceDiagnostic>> for Error {
235 fn from(e: ecow::EcoVec<SourceDiagnostic>) -> Self {
236 Error::new("", ErrKind::RawDiag(e), None)
237 }
238}
239
240impl std::error::Error for Error {}
241
242#[cfg(feature = "web")]
243impl ErrKindExt for wasm_bindgen::JsValue {
244 fn to_error_kind(self) -> ErrKind {
245 ErrKind::Msg(ecow::eco_format!("{self:?}"))
246 }
247}
248
249#[cfg(feature = "web")]
250impl From<Error> for wasm_bindgen::JsValue {
251 fn from(e: Error) -> Self {
252 js_sys::Error::new(&e.to_string()).into()
253 }
254}
255
256#[cfg(feature = "web")]
257impl From<&Error> for wasm_bindgen::JsValue {
258 fn from(e: &Error) -> Self {
259 js_sys::Error::new(&e.to_string()).into()
260 }
261}
262
263pub type Result<T, Err = Error> = std::result::Result<T, Err>;
265
266pub trait IgnoreLogging<T>: Sized {
268 fn log_error(self, msg: &str) -> Option<T>;
270 fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T>;
272}
273
274impl<T, E: std::fmt::Display> IgnoreLogging<T> for Result<T, E> {
275 fn log_error(self, msg: &str) -> Option<T> {
276 self.inspect_err(|e| log::error!("{msg}: {e}")).ok()
277 }
278
279 fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T> {
280 self.inspect_err(|e| log::error!("{}: {e}", f())).ok()
281 }
282}
283
284pub trait WithContext<T>: Sized {
286 fn context(self, loc: &'static str) -> Result<T>;
288
289 fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
291 where
292 F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
293}
294
295impl<T, E: ErrKindExt> WithContext<T> for Result<T, E> {
296 fn context(self, loc: &'static str) -> Result<T> {
297 self.map_err(|e| Error::new(loc, e.to_error_kind(), None))
298 }
299
300 fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
301 where
302 F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
303 {
304 self.map_err(|e| Error::new(loc, e.to_error_kind(), f()))
305 }
306}
307
308impl<T> WithContext<T> for Option<T> {
309 fn context(self, loc: &'static str) -> Result<T> {
310 self.ok_or_else(|| Error::new(loc, ErrKind::None, None))
311 }
312
313 fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
314 where
315 F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
316 {
317 self.ok_or_else(|| Error::new(loc, ErrKind::None, f()))
318 }
319}
320
321pub trait WithContextUntyped<T>: Sized {
323 fn context_ut(self, loc: &'static str) -> Result<T>;
325
326 fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
328 where
329 F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
330}
331
332impl<T, E: std::fmt::Display> WithContextUntyped<T> for Result<T, E> {
333 fn context_ut(self, loc: &'static str) -> Result<T> {
334 self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), None))
335 }
336
337 fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
338 where
339 F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
340 {
341 self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), f()))
342 }
343}
344
345pub mod prelude {
347 #![allow(missing_docs)]
348
349 use super::ErrKindExt;
350 use crate::Error;
351
352 pub use super::{IgnoreLogging, WithContext, WithContextUntyped};
353 pub use crate::{bail, Result};
354
355 pub fn map_string_err<T: ToString>(loc: &'static str) -> impl Fn(T) -> Error {
356 move |e| Error::new(loc, e.to_string().to_error_kind(), None)
357 }
358
359 pub fn map_into_err<S: ErrKindExt, T: Into<S>>(loc: &'static str) -> impl Fn(T) -> Error {
360 move |e| Error::new(loc, e.into().to_error_kind(), None)
361 }
362
363 pub fn map_err<T: ErrKindExt>(loc: &'static str) -> impl Fn(T) -> Error {
364 move |e| Error::new(loc, e.to_error_kind(), None)
365 }
366
367 pub fn wrap_err(loc: &'static str) -> impl Fn(Error) -> Error {
368 move |e| Error::new(loc, crate::ErrKind::Inner(e), None)
369 }
370
371 pub fn map_string_err_with_args<
372 T: ToString,
373 Args: IntoIterator<Item = (&'static str, String)>,
374 >(
375 loc: &'static str,
376 args: Args,
377 ) -> impl FnOnce(T) -> Error {
378 move |e| {
379 Error::new(
380 loc,
381 e.to_string().to_error_kind(),
382 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
383 )
384 }
385 }
386
387 pub fn map_into_err_with_args<
388 S: ErrKindExt,
389 T: Into<S>,
390 Args: IntoIterator<Item = (&'static str, String)>,
391 >(
392 loc: &'static str,
393 args: Args,
394 ) -> impl FnOnce(T) -> Error {
395 move |e| {
396 Error::new(
397 loc,
398 e.into().to_error_kind(),
399 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
400 )
401 }
402 }
403
404 pub fn map_err_with_args<T: ErrKindExt, Args: IntoIterator<Item = (&'static str, String)>>(
405 loc: &'static str,
406 args: Args,
407 ) -> impl FnOnce(T) -> Error {
408 move |e| {
409 Error::new(
410 loc,
411 e.to_error_kind(),
412 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
413 )
414 }
415 }
416
417 pub fn wrap_err_with_args<Args: IntoIterator<Item = (&'static str, String)>>(
418 loc: &'static str,
419 args: Args,
420 ) -> impl FnOnce(Error) -> Error {
421 move |e| {
422 Error::new(
423 loc,
424 crate::ErrKind::Inner(e),
425 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
426 )
427 }
428 }
429
430 pub fn _error_once(loc: &'static str, args: Box<[(&'static str, String)]>) -> Error {
431 Error::new(loc, crate::ErrKind::None, Some(args))
432 }
433
434 pub fn _msg(loc: &'static str, msg: EcoString) -> Error {
435 Error::new(loc, crate::ErrKind::Msg(msg), None)
436 }
437
438 pub use ecow::eco_format as _eco_format;
439
440 #[macro_export]
441 macro_rules! bail {
442 ($($arg:tt)+) => {{
443 let args = $crate::error::prelude::_eco_format!($($arg)+);
444 return Err($crate::error::prelude::_msg(file!(), args))
445 }};
446 }
447
448 #[macro_export]
449 macro_rules! error_once {
450 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
451 $crate::error::prelude::_error_once($loc, Box::new([$((stringify!($arg_key), $arg.to_string())),+]))
452 };
453 ($loc:expr $(,)?) => {
454 $crate::error::prelude::_error_once($loc, Box::new([]))
455 };
456 }
457
458 #[macro_export]
459 macro_rules! error_once_map {
460 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
461 $crate::error::prelude::map_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
462 };
463 ($loc:expr $(,)?) => {
464 $crate::error::prelude::map_err($loc)
465 };
466 }
467
468 #[macro_export]
469 macro_rules! error_once_map_string {
470 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
471 $crate::error::prelude::map_string_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
472 };
473 ($loc:expr $(,)?) => {
474 $crate::error::prelude::map_string_err($loc)
475 };
476 }
477
478 use ecow::EcoString;
479 pub use error_once;
480 pub use error_once_map;
481 pub use error_once_map_string;
482}
483
484#[test]
485fn test_send() {
486 fn is_send<T: Send>() {}
487 is_send::<Error>();
488}