README.md
1
2# `miette`
3
4You run miette? You run her code like the software? Oh. Oh! Error code for
5coder! Error code for One Thousand Lines!
6
7### About
8
9`miette` is a diagnostic library for Rust. It includes a series of
10traits/protocols that allow you to hook into its error reporting facilities,
11and even write your own error reports! It lets you define error types that
12can print out like this (or in any format you like!):
13
14<img src="https://raw.githubusercontent.com/zkat/miette/main/images/serde_json.png" alt="Hi! miette also includes a screen-reader-oriented diagnostic printer that's enabled in various situations, such as when you use NO_COLOR or CLICOLOR settings, or on CI. This behavior is also fully configurable and customizable. For example, this is what this particular diagnostic will look like when the narrated printer is enabled:
15\
16Error: Received some bad JSON from the source. Unable to parse.
17 Caused by: missing field `foo` at line 1 column 1700
18\
19Begin snippet for https://api.nuget.org/v3/registration5-gz-semver2/json.net/index.json starting
20at line 1, column 1659
21\
22snippet line 1: gs":["json"],"title":"","version":"1.0.0"},"packageContent":"https://api.nuget.o
23 highlight starting at line 1, column 1699: last parsing location
24\
25diagnostic help: This is a bug. It might be in ruget, or it might be in the
26source you're using, but it's definitely a bug and should be reported.
27diagnostic error code: ruget::api::bad_json
28" />
29
30> **NOTE: You must enable the `"fancy"` crate feature to get fancy report
31output like in the screenshots above.** You should only do this in your
32toplevel crate, as the fancy feature pulls in a number of dependencies that
33libraries and such might not want.
34
35### Table of Contents <!-- omit in toc -->
36
37- [About](#about)
38- [Features](#features)
39- [Installing](#installing)
40- [Example](#example)
41- [Using](#using)
42 - [... in libraries](#-in-libraries)
43 - [... in application code](#-in-application-code)
44 - [... in `main()`](#-in-main)
45 - [... diagnostic code URLs](#-diagnostic-code-urls)
46 - [... snippets](#-snippets)
47 - [... multiple related errors](#-multiple-related-errors)
48 - [... delayed source code](#-delayed-source-code)
49 - [... handler options](#-handler-options)
50 - [... dynamic diagnostics](#-dynamic-diagnostics)
51 - [... syntax highlighting](#-syntax-highlighting)
52- [Acknowledgements](#acknowledgements)
53- [License](#license)
54
55### Features
56
57- Generic [`Diagnostic`] protocol, compatible (and dependent on)
58 [`std::error::Error`].
59- Unique error codes on every [`Diagnostic`].
60- Custom links to get more details on error codes.
61- Super handy derive macro for defining diagnostic metadata.
62- Replacements for [`anyhow`](https://docs.rs/anyhow)/[`eyre`](https://docs.rs/eyre)
63 types [`Result`], [`Report`] and the [`miette!`] macro for the
64 `anyhow!`/`eyre!` macros.
65- Generic support for arbitrary [`SourceCode`]s for snippet data, with
66 default support for `String`s included.
67
68The `miette` crate also comes bundled with a default [`ReportHandler`] with
69the following features:
70
71- Fancy graphical [diagnostic output](#about), using ANSI/Unicode text
72- single- and multi-line highlighting support
73- Screen reader/braille support, gated on [`NO_COLOR`](http://no-color.org/),
74 and other heuristics.
75- Fully customizable graphical theming (or overriding the printers
76 entirely).
77- Cause chain printing
78- Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
79
80### Installing
81
82```sh
83$ cargo add miette
84```
85
86If you want to use the fancy printer in all these screenshots:
87
88```sh
89$ cargo add miette --features fancy
90```
91
92### Example
93
94```rust
95/*
96You can derive a `Diagnostic` from any `std::error::Error` type.
97
98`thiserror` is a great way to define them, and plays nicely with `miette`!
99*/
100use miette::{Diagnostic, SourceSpan};
101use thiserror::Error;
102
103#[derive(Error, Debug, Diagnostic)]
104#[error("oops!")]
105#[diagnostic(
106 code(oops::my::bad),
107 url(docsrs),
108 help("try doing it better next time?")
109)]
110struct MyBad {
111 // The Source that we're gonna be printing snippets out of.
112 // This can be a String if you don't have or care about file names.
113 #[source_code]
114 src: NamedSource<String>,
115 // Snippets and highlights can be included in the diagnostic!
116 #[label("This bit here")]
117 bad_bit: SourceSpan,
118}
119
120/*
121Now let's define a function!
122
123Use this `Result` type (or its expanded version) as the return type
124throughout your app (but NOT your libraries! Those should always return
125concrete types!).
126*/
127use miette::{NamedSource, Result};
128fn this_fails() -> Result<()> {
129 // You can use plain strings as a `Source`, or anything that implements
130 // the one-method `Source` trait.
131 let src = "source\n text\n here".to_string();
132 let len = src.len();
133
134 Err(MyBad {
135 src: NamedSource::new("bad_file.rs", src),
136 bad_bit: (9, 4).into(),
137 })?;
138
139 Ok(())
140}
141
142/*
143Now to get everything printed nicely, just return a `Result<()>`
144and you're all set!
145
146Note: You can swap out the default reporter for a custom one using
147`miette::set_hook()`
148*/
149fn pretend_this_is_main() -> Result<()> {
150 // kaboom~
151 this_fails()?;
152
153 Ok(())
154}
155```
156
157And this is the output you'll get if you run this program:
158
159<img src="https://raw.githubusercontent.com/zkat/miette/main/images/single-line-example.png" alt="
160Narratable printout:
161\
162Error: Types mismatched for operation.
163 Diagnostic severity: error
164Begin snippet starting at line 1, column 1
165\
166snippet line 1: 3 + "5"
167 label starting at line 1, column 1: int
168 label starting at line 1, column 1: doesn't support these values.
169 label starting at line 1, column 1: string
170diagnostic help: Change int or string to be the right types and try again.
171diagnostic code: nu::parser::unsupported_operation
172For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
173
174### Using
175
176#### ... in libraries
177
178`miette` is _fully compatible_ with library usage. Consumers who don't know
179about, or don't want, `miette` features can safely use its error types as
180regular [`std::error::Error`].
181
182We highly recommend using something like [`thiserror`](https://docs.rs/thiserror)
183to define unique error types and error wrappers for your library.
184
185While `miette` integrates smoothly with `thiserror`, it is _not required_.
186If you don't want to use the [`Diagnostic`] derive macro, you can implement
187the trait directly, just like with `std::error::Error`.
188
189```rust
190// lib/error.rs
191use miette::{Diagnostic, SourceSpan};
192use thiserror::Error;
193
194#[derive(Error, Diagnostic, Debug)]
195pub enum MyLibError {
196 #[error(transparent)]
197 #[diagnostic(code(my_lib::io_error))]
198 IoError(#[from] std::io::Error),
199
200 #[error("Oops it blew up")]
201 #[diagnostic(code(my_lib::bad_code))]
202 BadThingHappened,
203
204 #[error(transparent)]
205 // Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
206 #[diagnostic(transparent)]
207 AnotherError(#[from] AnotherError),
208}
209
210#[derive(Error, Diagnostic, Debug)]
211#[error("another error")]
212pub struct AnotherError {
213 #[label("here")]
214 pub at: SourceSpan
215}
216```
217
218Then, return this error type from all your fallible public APIs. It's a best
219practice to wrap any "external" error types in your error `enum` instead of
220using something like [`Report`] in a library.
221
222#### ... in application code
223
224Application code tends to work a little differently than libraries. You
225don't always need or care to define dedicated error wrappers for errors
226coming from external libraries and tools.
227
228For this situation, `miette` includes two tools: [`Report`] and
229[`IntoDiagnostic`]. They work in tandem to make it easy to convert regular
230`std::error::Error`s into [`Diagnostic`]s. Additionally, there's a
231[`Result`] type alias that you can use to be more terse.
232
233When dealing with non-`Diagnostic` types, you'll want to
234`.into_diagnostic()` them:
235
236```rust
237// my_app/lib/my_internal_file.rs
238use miette::{IntoDiagnostic, Result};
239use semver::Version;
240
241pub fn some_tool() -> Result<Version> {
242 Ok("1.2.x".parse().into_diagnostic()?)
243}
244```
245
246`miette` also includes an `anyhow`/`eyre`-style `Context`/`WrapErr` traits
247that you can import to add ad-hoc context messages to your `Diagnostic`s, as
248well, though you'll still need to use `.into_diagnostic()` to make use of
249it:
250
251```rust
252// my_app/lib/my_internal_file.rs
253use miette::{IntoDiagnostic, Result, WrapErr};
254use semver::Version;
255
256pub fn some_tool() -> Result<Version> {
257 Ok("1.2.x"
258 .parse()
259 .into_diagnostic()
260 .wrap_err("Parsing this tool's semver version failed.")?)
261}
262```
263
264To construct your own simple adhoc error use the [miette!] macro:
265```rust
266// my_app/lib/my_internal_file.rs
267use miette::{miette, IntoDiagnostic, Result, WrapErr};
268use semver::Version;
269
270pub fn some_tool() -> Result<Version> {
271 let version = "1.2.x";
272 Ok(version
273 .parse()
274 .map_err(|_| miette!("Invalid version {}", version))?)
275}
276```
277There are also similar [bail!] and [ensure!] macros.
278
279#### ... in `main()`
280
281`main()` is just like any other part of your application-internal code. Use
282`Result` as your return value, and it will pretty-print your diagnostics
283automatically.
284
285> **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
286output like in the screenshots here.** You should only do this in your
287toplevel crate, as the fancy feature pulls in a number of dependencies that
288libraries and such might not want.
289
290```rust
291use miette::{IntoDiagnostic, Result};
292use semver::Version;
293
294fn pretend_this_is_main() -> Result<()> {
295 let version: Version = "1.2.x".parse().into_diagnostic()?;
296 println!("{}", version);
297 Ok(())
298}
299```
300
301Please note: in order to get fancy diagnostic rendering with all the pretty
302colors and arrows, you should install `miette` with the `fancy` feature
303enabled:
304
305```toml
306miette = { version = "X.Y.Z", features = ["fancy"] }
307```
308
309Another way to display a diagnostic is by printing them using the debug formatter.
310This is, in fact, what returning diagnostics from main ends up doing.
311To do it yourself, you can write the following:
312
313```rust
314use miette::{IntoDiagnostic, Result};
315use semver::Version;
316
317fn just_a_random_function() {
318 let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
319 match version_result {
320 Err(e) => println!("{:?}", e),
321 Ok(version) => println!("{}", version),
322 }
323}
324```
325
326#### ... diagnostic code URLs
327
328`miette` supports providing a URL for individual diagnostics. This URL will
329be displayed as an actual link in supported terminals, like so:
330
331<img
332src="https://raw.githubusercontent.com/zkat/miette/main/images/code_linking.png"
333alt=" Example showing the graphical report printer for miette
334pretty-printing an error code. The code is underlined and followed by text
335saying to 'click here'. A hover tooltip shows a full-fledged URL that can be
336Ctrl+Clicked to open in a browser.
337\
338This feature is also available in the narratable printer. It will add a line
339after printing the error code showing a plain URL that you can visit.
340">
341
342To use this, you can add a `url()` sub-param to your `#[diagnostic]`
343attribute:
344
345```rust
346use miette::Diagnostic;
347use thiserror::Error;
348
349#[derive(Error, Diagnostic, Debug)]
350#[error("kaboom")]
351#[diagnostic(
352 code(my_app::my_error),
353 // You can do formatting!
354 url("https://my_website.com/error_codes#{}", self.code().unwrap())
355)]
356struct MyErr;
357```
358
359Additionally, if you're developing a library and your error type is exported
360from your crate's top level, you can use a special `url(docsrs)` option
361instead of manually constructing the URL. This will automatically create a
362link to this diagnostic on `docs.rs`, so folks can just go straight to your
363(very high quality and detailed!) documentation on this diagnostic:
364
365```rust
366use miette::Diagnostic;
367use thiserror::Error;
368
369#[derive(Error, Diagnostic, Debug)]
370#[diagnostic(
371 code(my_app::my_error),
372 // Will link users to https://docs.rs/my_crate/0.0.0/my_crate/struct.MyErr.html
373 url(docsrs)
374)]
375#[error("kaboom")]
376struct MyErr;
377```
378
379#### ... snippets
380
381Along with its general error handling and reporting features, `miette` also
382includes facilities for adding error spans/annotations/labels to your
383output. This can be very useful when an error is syntax-related, but you can
384even use it to print out sections of your own source code!
385
386To achieve this, `miette` defines its own lightweight [`SourceSpan`] type.
387This is a basic byte-offset and length into an associated [`SourceCode`]
388and, along with the latter, gives `miette` all the information it needs to
389pretty-print some snippets! You can also use your own `Into<SourceSpan>`
390types as label spans.
391
392The easiest way to define errors like this is to use the
393`derive(Diagnostic)` macro:
394
395```rust
396use miette::{Diagnostic, SourceSpan};
397use thiserror::Error;
398
399#[derive(Diagnostic, Debug, Error)]
400#[error("oops")]
401#[diagnostic(code(my_lib::random_error))]
402pub struct MyErrorType {
403 // The `Source` that miette will use.
404 #[source_code]
405 src: String,
406
407 // This will underline/mark the specific code inside the larger
408 // snippet context.
409 #[label = "This is the highlight"]
410 err_span: SourceSpan,
411
412 // You can add as many labels as you want.
413 // They'll be rendered sequentially.
414 #[label("This is bad")]
415 snip2: (usize, usize), // `(usize, usize)` is `Into<SourceSpan>`!
416
417 // Snippets can be optional, by using Option:
418 #[label("some text")]
419 snip3: Option<SourceSpan>,
420
421 // with or without label text
422 #[label]
423 snip4: Option<SourceSpan>,
424}
425```
426
427##### ... help text
428`miette` provides two facilities for supplying help text for your errors:
429
430The first is the `#[help()]` format attribute that applies to structs or
431enum variants:
432
433```rust
434use miette::Diagnostic;
435use thiserror::Error;
436
437#[derive(Debug, Diagnostic, Error)]
438#[error("welp")]
439#[diagnostic(help("try doing this instead"))]
440struct Foo;
441```
442
443The other is by programmatically supplying the help text as a field to
444your diagnostic:
445
446```rust
447use miette::Diagnostic;
448use thiserror::Error;
449
450#[derive(Debug, Diagnostic, Error)]
451#[error("welp")]
452#[diagnostic()]
453struct Foo {
454 #[help]
455 advice: Option<String>, // Can also just be `String`
456}
457
458let err = Foo {
459 advice: Some("try doing this instead".to_string()),
460};
461```
462
463#### ... multiple related errors
464
465`miette` supports collecting multiple errors into a single diagnostic, and
466printing them all together nicely.
467
468To do so, use the `#[related]` tag on any `IntoIter` field in your
469`Diagnostic` type:
470
471```rust
472use miette::Diagnostic;
473use thiserror::Error;
474
475#[derive(Debug, Error, Diagnostic)]
476#[error("oops")]
477struct MyError {
478 #[related]
479 others: Vec<MyError>,
480}
481```
482
483#### ... delayed source code
484
485Sometimes it makes sense to add source code to the error message later.
486One option is to use [`with_source_code()`](Report::with_source_code)
487method for that:
488
489```rust
490use miette::{Diagnostic, SourceSpan};
491use thiserror::Error;
492
493#[derive(Diagnostic, Debug, Error)]
494#[error("oops")]
495#[diagnostic()]
496pub struct MyErrorType {
497 // Note: label but no source code
498 #[label]
499 err_span: SourceSpan,
500}
501
502fn do_something() -> miette::Result<()> {
503 // This function emits actual error with label
504 return Err(MyErrorType {
505 err_span: (7..11).into(),
506 })?;
507}
508
509fn main() -> miette::Result<()> {
510 do_something().map_err(|error| {
511 // And this code provides the source code for inner error
512 error.with_source_code(String::from("source code"))
513 })
514}
515```
516
517Also source code can be provided by a wrapper type. This is especially
518useful in combination with `related`, when multiple errors should be
519emitted at the same time:
520
521```rust
522use miette::{Diagnostic, Report, SourceSpan};
523use thiserror::Error;
524
525#[derive(Diagnostic, Debug, Error)]
526#[error("oops")]
527#[diagnostic()]
528pub struct InnerError {
529 // Note: label but no source code
530 #[label]
531 err_span: SourceSpan,
532}
533
534#[derive(Diagnostic, Debug, Error)]
535#[error("oops: multiple errors")]
536#[diagnostic()]
537pub struct MultiError {
538 // Note source code by no labels
539 #[source_code]
540 source_code: String,
541 // The source code above is used for these errors
542 #[related]
543 related: Vec<InnerError>,
544}
545
546fn do_something() -> Result<(), Vec<InnerError>> {
547 Err(vec![
548 InnerError {
549 err_span: (0..6).into(),
550 },
551 InnerError {
552 err_span: (7..11).into(),
553 },
554 ])
555}
556
557fn main() -> miette::Result<()> {
558 do_something().map_err(|err_list| MultiError {
559 source_code: "source code".into(),
560 related: err_list,
561 })?;
562 Ok(())
563}
564```
565
566#### ... Diagnostic-based error sources.
567
568When one uses the `#[source]` attribute on a field, that usually comes
569from `thiserror`, and implements a method for
570[`std::error::Error::source`]. This works in many cases, but it's lossy:
571if the source of the diagnostic is a diagnostic itself, the source will
572simply be treated as an `std::error::Error`.
573
574While this has no effect on the existing _reporters_, since they don't use
575that information right now, APIs who might want this information will have
576no access to it.
577
578If it's important for you for this information to be available to users,
579you can use `#[diagnostic_source]` alongside `#[source]`. Not that you
580will likely want to use _both_:
581
582```rust
583use miette::Diagnostic;
584use thiserror::Error;
585
586#[derive(Debug, Diagnostic, Error)]
587#[error("MyError")]
588struct MyError {
589 #[source]
590 #[diagnostic_source]
591 the_cause: OtherError,
592}
593
594#[derive(Debug, Diagnostic, Error)]
595#[error("OtherError")]
596struct OtherError;
597```
598
599#### ... handler options
600
601[`MietteHandler`] is the default handler, and is very customizable. In
602most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
603instead of falling back to your own custom handler.
604
605Usage is like so:
606
607```rust
608miette::set_hook(Box::new(|_| {
609 Box::new(
610 miette::MietteHandlerOpts::new()
611 .terminal_links(true)
612 .unicode(false)
613 .context_lines(3)
614 .tab_width(4)
615 .break_words(true)
616 .build(),
617 )
618}))
619
620```
621
622See the docs for [`MietteHandlerOpts`] for more details on what you can
623customize!
624
625#### ... dynamic diagnostics
626
627If you...
628- ...don't know all the possible errors upfront
629- ...need to serialize/deserialize errors
630then you may want to use [`miette!`], [`diagnostic!`] macros or
631[`MietteDiagnostic`] directly to create diagnostic on the fly.
632
633```rust
634
635let source = "2 + 2 * 2 = 8".to_string();
636let report = miette!(
637 labels = vec[
638 LabeledSpan::at(12..13, "this should be 6"),
639 ],
640 help = "'*' has greater precedence than '+'",
641 "Wrong answer"
642).with_source_code(source);
643println!("{:?}", report)
644```
645
646#### ... syntax highlighting
647
648`miette` can be configured to highlight syntax in source code snippets.
649
650<!-- TODO: screenshot goes here once default Theme is decided -->
651
652To use the built-in highlighting functionality, you must enable the
653`syntect-highlighter` crate feature. When this feature is enabled, `miette` will
654automatically use the [`syntect`] crate to highlight the `#[source_code]`
655field of your [`Diagnostic`].
656
657Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
658* [language()](SpanContents::language) - Provides the name of the language
659 as a string. For example `"Rust"` will indicate Rust syntax highlighting.
660 You can set the language of the [`SpanContents`] produced by a
661 [`NamedSource`] via the [`with_language`](NamedSource::with_language)
662 method.
663* [name()](SpanContents::name) - In the absence of an explicitly set
664 language, the name is assumed to contain a file name or file path.
665 The highlighter will check for a file extension at the end of the name and
666 try to guess the syntax from that.
667
668If you want to use a custom highlighter, you can provide a custom
669implementation of the [`Highlighter`](highlighters::Highlighter)
670trait to [`MietteHandlerOpts`] by calling the
671[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
672method. See the [`highlighters`] module docs for more details.
673
674### MSRV
675
676This crate requires rustc 1.70.0 or later.
677
678### Acknowledgements
679
680`miette` was not developed in a void. It owes enormous credit to various
681other projects and their authors:
682
683- [`anyhow`](http://crates.io/crates/anyhow) and [`color-eyre`](https://crates.io/crates/color-eyre):
684 these two enormously influential error handling libraries have pushed
685 forward the experience of application-level error handling and error
686 reporting. `miette`'s `Report` type is an attempt at a very very rough
687 version of their `Report` types.
688- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
689 for library-level error definitions, and for being the inspiration behind
690 `miette`'s derive macro.
691- `rustc` and [@estebank](https://github.com/estebank) for their
692 state-of-the-art work in compiler diagnostics.
693- [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
694 _pretty_ these diagnostics can really look!
695
696### License
697
698`miette` is released to the Rust community under the [Apache license
6992.0](./LICENSE).
700
701It also includes code taken from [`eyre`](https://github.com/yaahc/eyre),
702and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
703under the Apache License. Some code is taken from
704[`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
705
706[`miette!`]: https://docs.rs/miette/latest/miette/macro.miette.html
707[`diagnostic!`]: https://docs.rs/miette/latest/miette/macro.diagnostic.html
708[`std::error::Error`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html
709[`Diagnostic`]: https://docs.rs/miette/latest/miette/trait.Diagnostic.html
710[`IntoDiagnostic`]: https://docs.rs/miette/latest/miette/trait.IntoDiagnostic.html
711[`MietteHandlerOpts`]: https://docs.rs/miette/latest/miette/struct.MietteHandlerOpts.html
712[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
713[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
714[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
715[`ReportHandler`]: https://docs.rs/miette/latest/miette/trait.ReportHandler.html
716[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
717[`SourceCode`]: https://docs.rs/miette/latest/miette/trait.SourceCode.html
718[`SourceSpan`]: https://docs.rs/miette/latest/miette/struct.SourceSpan.html
719