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(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    #[cfg(feature = "typst")]
194    pub fn diagnostics(&self) -> Option<&[SourceDiagnostic]> {
195        match &self.err.kind {
196            ErrKind::RawDiag(diag) => Some(diag),
197            _ => None,
198        }
199    }
200}
201
202impl fmt::Debug for Error {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        <Self as fmt::Display>::fmt(self, f)
205    }
206}
207
208macro_rules! write_with_args {
209    ($f:expr, $args:expr, $fmt:expr  $(, $arg:expr)*) => {
210        if let Some(args) = $args.as_ref() {
211            write!($f, "{}, with {:?}", format_args!($fmt $(, $arg)*), args)
212        } else {
213            write!($f, $fmt $(, $arg)*)
214        }
215    };
216}
217
218impl fmt::Display for Error {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        let err = &self.err;
221
222        if err.loc.is_empty() {
223            match &err.kind {
224                ErrKind::Msg(msg) => {
225                    if msg.is_empty() {
226                        write_with_args!(f, err.args, "{}", err.loc)
227                    } else {
228                        write_with_args!(f, err.args, "{}: {msg}", err.loc)
229                    }
230                }
231                #[cfg(feature = "typst")]
232                ErrKind::RawDiag(diag) => {
233                    write_with_args!(f, err.args, "{diag:?}")
234                }
235                ErrKind::Diag(diag) => {
236                    write_with_args!(f, err.args, "{}", diag.message)
237                }
238                ErrKind::Inner(e) => write_with_args!(f, err.args, "{e}"),
239                ErrKind::None => write_with_args!(f, err.args, "unknown error"),
240            }
241        } else {
242            match &err.kind {
243                ErrKind::Msg(msg) => {
244                    if msg.is_empty() {
245                        write_with_args!(f, err.args, "{}", err.loc)
246                    } else {
247                        write_with_args!(f, err.args, "{}: {msg}", err.loc)
248                    }
249                }
250                #[cfg(feature = "typst")]
251                ErrKind::RawDiag(diag) => {
252                    write_with_args!(f, err.args, "{}: {diag:?}", err.loc)
253                }
254                ErrKind::Diag(diag) => {
255                    write_with_args!(f, err.args, "{}: {}", err.loc, diag.message)
256                }
257                ErrKind::Inner(e) => write_with_args!(f, err.args, "{}: {}", err.loc, e),
258                ErrKind::None => write_with_args!(f, err.args, "{}", err.loc),
259            }
260        }
261    }
262}
263
264impl From<anyhow::Error> for Error {
265    fn from(e: anyhow::Error) -> Self {
266        Error::new("", e.to_string().to_error_kind(), None)
267    }
268}
269
270#[cfg(feature = "typst")]
271impl From<ecow::EcoVec<SourceDiagnostic>> for Error {
272    fn from(e: ecow::EcoVec<SourceDiagnostic>) -> Self {
273        Error::new("", ErrKind::RawDiag(e), None)
274    }
275}
276
277impl std::error::Error for Error {}
278
279#[cfg(feature = "web")]
280impl ErrKindExt for wasm_bindgen::JsValue {
281    fn to_error_kind(self) -> ErrKind {
282        ErrKind::Msg(ecow::eco_format!("{self:?}"))
283    }
284}
285
286#[cfg(feature = "web")]
287impl From<Error> for wasm_bindgen::JsValue {
288    fn from(e: Error) -> Self {
289        js_sys::Error::new(&e.to_string()).into()
290    }
291}
292
293#[cfg(feature = "web")]
294impl From<&Error> for wasm_bindgen::JsValue {
295    fn from(e: &Error) -> Self {
296        js_sys::Error::new(&e.to_string()).into()
297    }
298}
299
300pub type Result<T, Err = Error> = std::result::Result<T, Err>;
302
303pub trait IgnoreLogging<T>: Sized {
305    fn log_error(self, msg: &str) -> Option<T>;
307    fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T>;
309}
310
311impl<T, E: std::fmt::Display> IgnoreLogging<T> for Result<T, E> {
312    fn log_error(self, msg: &str) -> Option<T> {
313        self.inspect_err(|e| log::error!("{msg}: {e}")).ok()
314    }
315
316    fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T> {
317        self.inspect_err(|e| log::error!("{}: {e}", f())).ok()
318    }
319}
320
321impl<T> IgnoreLogging<T> for Option<T> {
322    fn log_error(self, msg: &str) -> Option<T> {
323        self.or_else(|| {
324            log::error!("{msg}");
325            None
326        })
327    }
328
329    fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T> {
330        self.or_else(|| {
331            log::error!("{}", f());
332            None
333        })
334    }
335}
336
337pub trait WithContext<T>: Sized {
339    fn context(self, loc: &'static str) -> Result<T>;
341
342    fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
344    where
345        F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
346}
347
348impl<T, E: ErrKindExt> WithContext<T> for Result<T, E> {
349    fn context(self, loc: &'static str) -> Result<T> {
350        self.map_err(|e| Error::new(loc, e.to_error_kind(), None))
351    }
352
353    fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
354    where
355        F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
356    {
357        self.map_err(|e| Error::new(loc, e.to_error_kind(), f()))
358    }
359}
360
361impl<T> WithContext<T> for Option<T> {
362    fn context(self, loc: &'static str) -> Result<T> {
363        self.ok_or_else(|| Error::new(loc, ErrKind::None, None))
364    }
365
366    fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
367    where
368        F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
369    {
370        self.ok_or_else(|| Error::new(loc, ErrKind::None, f()))
371    }
372}
373
374pub trait WithContextUntyped<T>: Sized {
376    fn context_ut(self, loc: &'static str) -> Result<T>;
378
379    fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
381    where
382        F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
383}
384
385impl<T, E: std::fmt::Display> WithContextUntyped<T> for Result<T, E> {
386    fn context_ut(self, loc: &'static str) -> Result<T> {
387        self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), None))
388    }
389
390    fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
391    where
392        F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
393    {
394        self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), f()))
395    }
396}
397
398pub mod prelude {
400
401    use super::ErrKindExt;
402    use crate::Error;
403
404    pub use super::{IgnoreLogging, WithContext, WithContextUntyped};
405    pub use crate::{Result, bail};
406
407    pub fn map_string_err<T: ToString>(loc: &'static str) -> impl Fn(T) -> Error {
409        move |e| Error::new(loc, e.to_string().to_error_kind(), None)
410    }
411
412    pub fn map_into_err<S: ErrKindExt, T: Into<S>>(loc: &'static str) -> impl Fn(T) -> Error {
414        move |e| Error::new(loc, e.into().to_error_kind(), None)
415    }
416
417    pub fn map_err<T: ErrKindExt>(loc: &'static str) -> impl Fn(T) -> Error {
419        move |e| Error::new(loc, e.to_error_kind(), None)
420    }
421
422    pub fn wrap_err(loc: &'static str) -> impl Fn(Error) -> Error {
424        move |e| Error::new(loc, crate::ErrKind::Inner(e), None)
425    }
426
427    pub fn map_string_err_with_args<
429        T: ToString,
430        Args: IntoIterator<Item = (&'static str, String)>,
431    >(
432        loc: &'static str,
433        args: Args,
434    ) -> impl FnOnce(T) -> Error {
435        move |e| {
436            Error::new(
437                loc,
438                e.to_string().to_error_kind(),
439                Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
440            )
441        }
442    }
443
444    pub fn map_into_err_with_args<
446        S: ErrKindExt,
447        T: Into<S>,
448        Args: IntoIterator<Item = (&'static str, String)>,
449    >(
450        loc: &'static str,
451        args: Args,
452    ) -> impl FnOnce(T) -> Error {
453        move |e| {
454            Error::new(
455                loc,
456                e.into().to_error_kind(),
457                Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
458            )
459        }
460    }
461
462    pub fn map_err_with_args<T: ErrKindExt, Args: IntoIterator<Item = (&'static str, String)>>(
464        loc: &'static str,
465        args: Args,
466    ) -> impl FnOnce(T) -> Error {
467        move |e| {
468            Error::new(
469                loc,
470                e.to_error_kind(),
471                Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
472            )
473        }
474    }
475
476    pub fn wrap_err_with_args<Args: IntoIterator<Item = (&'static str, String)>>(
478        loc: &'static str,
479        args: Args,
480    ) -> impl FnOnce(Error) -> Error {
481        move |e| {
482            Error::new(
483                loc,
484                crate::ErrKind::Inner(e),
485                Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
486            )
487        }
488    }
489
490    pub fn _error_once(loc: &'static str, args: Box<[(&'static str, String)]>) -> Error {
492        Error::new(loc, crate::ErrKind::None, Some(args))
493    }
494
495    pub fn _msg(loc: &'static str, msg: EcoString) -> Error {
497        Error::new(loc, crate::ErrKind::Msg(msg), None)
498    }
499
500    pub use ecow::eco_format as _eco_format;
502
503    #[macro_export]
505    macro_rules! bail {
506        ($($arg:tt)+) => {{
507            let args = $crate::error::prelude::_eco_format!($($arg)+);
508            return Err($crate::error::prelude::_msg(concat!(file!(), ":", line!(), ":", column!()), args))
509        }};
510    }
511
512    #[macro_export]
514    macro_rules! error_once {
515        ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
516            $crate::error::prelude::_error_once($loc, Box::new([$((stringify!($arg_key), $arg.to_string())),+]))
517        };
518        ($loc:expr $(,)?) => {
519            $crate::error::prelude::_error_once($loc, Box::new([]))
520        };
521    }
522
523    #[macro_export]
525    macro_rules! error_once_map {
526        ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
527            $crate::error::prelude::map_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
528        };
529        ($loc:expr $(,)?) => {
530            $crate::error::prelude::map_err($loc)
531        };
532    }
533
534    #[macro_export]
536    macro_rules! error_once_map_string {
537        ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
538            $crate::error::prelude::map_string_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
539        };
540        ($loc:expr $(,)?) => {
541            $crate::error::prelude::map_string_err($loc)
542        };
543    }
544
545    use ecow::EcoString;
546    pub use error_once;
547    pub use error_once_map;
548    pub use error_once_map_string;
549}
550
551#[test]
552fn test_send() {
553    fn is_send<T: Send>() {}
554    is_send::<Error>();
555}