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
398impl<T> WithContextUntyped<T> for Option<T> {
399 fn context_ut(self, loc: &'static str) -> Result<T> {
400 self.ok_or_else(|| Error::new(loc, ErrKind::None, None))
401 }
402
403 fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
404 where
405 F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
406 {
407 self.ok_or_else(|| Error::new(loc, ErrKind::None, f()))
408 }
409}
410
411pub mod prelude {
413
414 use super::ErrKindExt;
415 use crate::Error;
416
417 pub use super::{IgnoreLogging, WithContext, WithContextUntyped};
418 pub use crate::{Result, bail};
419
420 pub fn map_string_err<T: ToString>(loc: &'static str) -> impl Fn(T) -> Error {
422 move |e| Error::new(loc, e.to_string().to_error_kind(), None)
423 }
424
425 pub fn map_into_err<S: ErrKindExt, T: Into<S>>(loc: &'static str) -> impl Fn(T) -> Error {
427 move |e| Error::new(loc, e.into().to_error_kind(), None)
428 }
429
430 pub fn map_err<T: ErrKindExt>(loc: &'static str) -> impl Fn(T) -> Error {
432 move |e| Error::new(loc, e.to_error_kind(), None)
433 }
434
435 pub fn wrap_err(loc: &'static str) -> impl Fn(Error) -> Error {
437 move |e| Error::new(loc, crate::ErrKind::Inner(e), None)
438 }
439
440 pub fn map_string_err_with_args<
442 T: ToString,
443 Args: IntoIterator<Item = (&'static str, String)>,
444 >(
445 loc: &'static str,
446 args: Args,
447 ) -> impl FnOnce(T) -> Error {
448 move |e| {
449 Error::new(
450 loc,
451 e.to_string().to_error_kind(),
452 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
453 )
454 }
455 }
456
457 pub fn map_into_err_with_args<
459 S: ErrKindExt,
460 T: Into<S>,
461 Args: IntoIterator<Item = (&'static str, String)>,
462 >(
463 loc: &'static str,
464 args: Args,
465 ) -> impl FnOnce(T) -> Error {
466 move |e| {
467 Error::new(
468 loc,
469 e.into().to_error_kind(),
470 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
471 )
472 }
473 }
474
475 pub fn map_err_with_args<T: ErrKindExt, Args: IntoIterator<Item = (&'static str, String)>>(
477 loc: &'static str,
478 args: Args,
479 ) -> impl FnOnce(T) -> Error {
480 move |e| {
481 Error::new(
482 loc,
483 e.to_error_kind(),
484 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
485 )
486 }
487 }
488
489 pub fn wrap_err_with_args<Args: IntoIterator<Item = (&'static str, String)>>(
491 loc: &'static str,
492 args: Args,
493 ) -> impl FnOnce(Error) -> Error {
494 move |e| {
495 Error::new(
496 loc,
497 crate::ErrKind::Inner(e),
498 Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
499 )
500 }
501 }
502
503 pub fn _error_once(loc: &'static str, args: Box<[(&'static str, String)]>) -> Error {
505 Error::new(loc, crate::ErrKind::None, Some(args))
506 }
507
508 pub fn _msg(loc: &'static str, msg: EcoString) -> Error {
510 Error::new(loc, crate::ErrKind::Msg(msg), None)
511 }
512
513 pub use ecow::eco_format as _eco_format;
515
516 #[macro_export]
518 macro_rules! bail {
519 ($($arg:tt)+) => {{
520 let args = $crate::error::prelude::_eco_format!($($arg)+);
521 return Err($crate::error::prelude::_msg(concat!(file!(), ":", line!(), ":", column!()), args))
522 }};
523 }
524
525 #[macro_export]
527 macro_rules! error_once {
528 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
529 $crate::error::prelude::_error_once($loc, Box::new([$((stringify!($arg_key), $arg.to_string())),+]))
530 };
531 ($loc:expr $(,)?) => {
532 $crate::error::prelude::_error_once($loc, Box::new([]))
533 };
534 }
535
536 #[macro_export]
538 macro_rules! error_once_map {
539 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
540 $crate::error::prelude::map_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
541 };
542 ($loc:expr $(,)?) => {
543 $crate::error::prelude::map_err($loc)
544 };
545 }
546
547 #[macro_export]
549 macro_rules! error_once_map_string {
550 ($loc:expr, $($arg_key:ident: $arg:expr),+ $(,)?) => {
551 $crate::error::prelude::map_string_err_with_args($loc, [$((stringify!($arg_key), $arg.to_string())),+])
552 };
553 ($loc:expr $(,)?) => {
554 $crate::error::prelude::map_string_err($loc)
555 };
556 }
557
558 use ecow::EcoString;
559 pub use error_once;
560 pub use error_once_map;
561 pub use error_once_map_string;
562}
563
564#[test]
565fn test_send() {
566 fn is_send<T: Send>() {}
567 is_send::<Error>();
568}