61 个版本 (稳定版)
7.2.0 | 2024年3月7日 |
---|---|
6.0.1 | 2024年2月4日 |
5.10.0 | 2023年7月16日 |
5.6.0 | 2023年3月14日 |
0.13.0 | 2021年8月21日 |
#2 在 Rust 模式
每月下载量 945,013
在 1,146 个crate中使用了 (直接使用 448)
300KB
5.5K SLoC
miette
你在运行 miette 吗?你像运行软件一样运行她的代码?哦。哦!为程序员准备的错误代码!为千行代码准备的错误代码!
关于
miette
是一个 Rust 的诊断库。它包含一系列特质/协议,允许您钩入其错误报告功能,甚至编写自己的错误报告!它允许您定义可以打印出如下所示的错误类型(或您喜欢的任何格式!)
注意:您必须启用
"fancy"
crate 功能以获取像上面截图那样的花哨报告输出。 您应该在顶层 crate 中这样做,因为花哨功能会引入一些依赖项,这些依赖项可能不适用于库等。
目录
功能
- 泛型
Diagnostic
协议,兼容(并依赖于)std::error::Error
。 - 每个
Diagnostic
都有独特的错误代码。 - 自定义链接以获取错误代码的更多详细信息。
- 用于定义诊断元数据的超方便的 derive 宏。
- 对
anyhow
/eyre
类型、Result
、Report
以及用于anyhow!
/eyre!
宏的miette!
宏的替代品。 - 对任意
SourceCode
的片段数据进行泛型支持,默认支持String
。
miette
包还捆绑了一个默认的 ReportHandler
,具有以下功能:
- 使用 ANSI/Unicode 文本进行花哨的图形诊断输出
- 单行和多行高亮支持
- 屏幕阅读器/盲文支持,通过
NO_COLOR
和其他启发式方法进行控制。 - 完全可定制的图形主题(或完全覆盖打印器)。
- 原因链打印
- 将诊断代码转换为支持终端中的链接。
安装
$ cargo add miette
如果您想在所有这些屏幕截图中使用花哨的打印器
$ cargo add miette --features fancy
示例
/*
You can derive a `Diagnostic` from any `std::error::Error` type.
`thiserror` is a great way to define them, and plays nicely with `miette`!
*/
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
#[derive(Error, Debug, Diagnostic)]
#[error("oops!")]
#[diagnostic(
code(oops::my::bad),
url(docsrs),
help("try doing it better next time?")
)]
struct MyBad {
// The Source that we're gonna be printing snippets out of.
// This can be a String if you don't have or care about file names.
#[source_code]
src: NamedSource<String>,
// Snippets and highlights can be included in the diagnostic!
#[label("This bit here")]
bad_bit: SourceSpan,
}
/*
Now let's define a function!
Use this `Result` type (or its expanded version) as the return type
throughout your app (but NOT your libraries! Those should always return
concrete types!).
*/
use miette::{NamedSource, Result};
fn this_fails() -> Result<()> {
// You can use plain strings as a `Source`, or anything that implements
// the one-method `Source` trait.
let src = "source\n text\n here".to_string();
let len = src.len();
Err(MyBad {
src: NamedSource::new("bad_file.rs", src),
bad_bit: (9, 4).into(),
})?;
Ok(())
}
/*
Now to get everything printed nicely, just return a `Result<()>`
and you're all set!
Note: You can swap out the default reporter for a custom one using
`miette::set_hook()`
*/
fn pretend_this_is_main() -> Result<()> {
// kaboom~
this_fails()?;
Ok(())
}
运行此程序后,您将获得以下输出
使用
... 在库中
miette
与库使用完全兼容。不知道或不想使用 miette
功能的消费者可以安全地将其错误类型作为常规 std::error::Error
使用。
我们强烈建议使用类似 thiserror
的工具来为您的库定义独特的错误类型和错误包装器。
虽然 miette
与 thiserror
集成良好,但它不是必需的。如果您不想使用 Diagnostic
derive 宏,您可以像处理 std::error::Error
一样直接实现该特性。
// lib/error.rs
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
pub enum MyLibError {
#[error(transparent)]
#[diagnostic(code(my_lib::io_error))]
IoError(#[from] std::io::Error),
#[error("Oops it blew up")]
#[diagnostic(code(my_lib::bad_code))]
BadThingHappened,
#[error(transparent)]
// Use `#[diagnostic(transparent)]` to wrap another [`Diagnostic`]. You won't see labels otherwise
#[diagnostic(transparent)]
AnotherError(#[from] AnotherError),
}
#[derive(Error, Diagnostic, Debug)]
#[error("another error")]
pub struct AnotherError {
#[label("here")]
pub at: SourceSpan
}
然后,从所有您的易出错公共 API 返回此错误类型。将任何“外部”错误类型包装在您的错误 enum
中,而不是在库中使用像 Report
这样的东西,是一种最佳实践。
... 在应用程序代码中
应用程序代码通常与库的工作方式略有不同。您不一定需要或关心为来自外部库和工具的错误定义专用错误包装器。
对于这种情况,miette
包含两个工具:Report
和 IntoDiagnostic
。它们协同工作,使将常规 std::error::Error
转换为 Diagnostic
变得非常容易。此外,还有一个可以用来更简洁的 Result
类型别名。
处理非 Diagnostic
类型时,您需要使用 .into_diagnostic()
方法将它们转换为诊断
// my_app/lib/my_internal_file.rs
use miette::{IntoDiagnostic, Result};
use semver::Version;
pub fn some_tool() -> Result<Version> {
Ok("1.2.x".parse().into_diagnostic()?)
}
miette
还包括类似 anyhow
/eyre
风格的 Context
/WrapErr
特性,您可以将它们导入以向您的 Diagnostic
添加自定义上下文消息,尽管您仍然需要使用 .into_diagnostic()
来使用它
// my_app/lib/my_internal_file.rs
use miette::{IntoDiagnostic, Result, WrapErr};
use semver::Version;
pub fn some_tool() -> Result<Version> {
Ok("1.2.x"
.parse()
.into_diagnostic()
.wrap_err("Parsing this tool's semver version failed.")?)
}
要构建自己的简单自定义错误,请使用 [miette!] 宏
// my_app/lib/my_internal_file.rs
use miette::{miette, IntoDiagnostic, Result, WrapErr};
use semver::Version;
pub fn some_tool() -> Result<Version> {
let version = "1.2.x";
Ok(version
.parse()
.map_err(|_| miette!("Invalid version {}", version))?)
}
还有类似的 [bail!] 和 [ensure!] 宏。
... 在 main()
main()
就像您应用程序内部的其他任何部分一样。使用 Result
作为您的返回值,它将自动以格式化方式打印您的诊断信息。
注意:您必须启用
"fancy"
crate 功能以获取类似于此处截图中的花哨报告输出。**您应该只在您的顶级 crate 中这样做,因为花哨功能会引入许多库可能不需要的依赖项。
use miette::{IntoDiagnostic, Result};
use semver::Version;
fn pretend_this_is_main() -> Result<()> {
let version: Version = "1.2.x".parse().into_diagnostic()?;
println!("{}", version);
Ok(())
}
请注意:要获得具有所有漂亮颜色和箭头的花哨诊断渲染,您应该启用 fancy
功能安装 miette
miette = { version = "X.Y.Z", features = ["fancy"] }
另一种显示诊断的方法是使用调试格式化程序打印它们。实际上,这就是从 main 返回诊断所做的事情。要自己这样做,您可以编写以下内容
use miette::{IntoDiagnostic, Result};
use semver::Version;
fn just_a_random_function() {
let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
match version_result {
Err(e) => println!("{:?}", e),
Ok(version) => println!("{}", version),
}
}
... 诊断代码 URL
miette
支持为单个诊断提供 URL。此 URL 将在支持的终端中显示为实际链接,如下所示
要使用此功能,您可以将 url()
子参数添加到您的 #[diagnostic]
属性
use miette::Diagnostic;
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
#[error("kaboom")]
#[diagnostic(
code(my_app::my_error),
// You can do formatting!
url("https://my_website.com/error_codes#{}", self.code().unwrap())
)]
struct MyErr;
此外,如果您正在开发库,并且您的错误类型是从 crate 的顶级导出的,则可以使用特殊的 url(docsrs)
选项而不是手动构造 URL。这将自动在 docs.rs
上创建一个链接到该诊断的链接,因此人们可以直接转到关于此诊断的(非常高质量和详细的!)文档
use miette::Diagnostic;
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
#[diagnostic(
code(my_app::my_error),
// Will link users to https://docs.rs/my_crate/0.0.0/my_crate/struct.MyErr.html
url(docsrs)
)]
#[error("kaboom")]
struct MyErr;
... 代码片段
除了其通用的错误处理和报告功能外,miette
还包括添加错误范围/注释/标签到您输出的功能。当错误与语法相关时,这非常有用,但您甚至可以使用它来打印您自己的源代码部分!
为了实现这一点,miette
定义了自己的轻量级 SourceSpan
类型。这是一个关联 SourceCode
的基本字节偏移量和长度,并且与后者一起,为 miette
提供了所有打印某些片段所需的信息!您还可以使用您自己的 Into<SourceSpan>
类型作为标签范围。
定义此类错误的简单方法是使用 derive(Diagnostic)
宏
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
#[derive(Diagnostic, Debug, Error)]
#[error("oops")]
#[diagnostic(code(my_lib::random_error))]
pub struct MyErrorType {
// The `Source` that miette will use.
#[source_code]
src: String,
// This will underline/mark the specific code inside the larger
// snippet context.
#[label = "This is the highlight"]
err_span: SourceSpan,
// You can add as many labels as you want.
// They'll be rendered sequentially.
#[label("This is bad")]
snip2: (usize, usize), // `(usize, usize)` is `Into<SourceSpan>`!
// Snippets can be optional, by using Option:
#[label("some text")]
snip3: Option<SourceSpan>,
// with or without label text
#[label]
snip4: Option<SourceSpan>,
}
... 帮助文本
miette
为您提供了两种为错误提供帮助文本的方法
第一种是应用于结构体或枚举变体的 #[help()]
格式属性
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(help("try doing this instead"))]
struct Foo;
另一种是通过程序性地将帮助文本作为字段提供给您诊断
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic()]
struct Foo {
#[help]
advice: Option<String>, // Can also just be `String`
}
let err = Foo {
advice: Some("try doing this instead".to_string()),
};
... 严重级别
miette
提供了一种设置诊断严重级别的方法。
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(severity(Warning))]
struct Foo;
... 多个相关错误
miette
支持将多个错误收集到一个诊断中,并一起优雅地打印它们。
要这样做,请在您的 Diagnostic
类型中的任何 IntoIter
字段上使用 #[related]
标签
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Error, Diagnostic)]
#[error("oops")]
struct MyError {
#[related]
others: Vec<MyError>,
}
... 延迟源代码
有时在错误消息中添加源代码是有意义的。一个选项是使用 with_source_code()
方法来实现
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
#[derive(Diagnostic, Debug, Error)]
#[error("oops")]
#[diagnostic()]
pub struct MyErrorType {
// Note: label but no source code
#[label]
err_span: SourceSpan,
}
fn do_something() -> miette::Result<()> {
// This function emits actual error with label
return Err(MyErrorType {
err_span: (7..11).into(),
})?;
}
fn main() -> miette::Result<()> {
do_something().map_err(|error| {
// And this code provides the source code for inner error
error.with_source_code(String::from("source code"))
})
}
此外,源代码也可以由包装类型提供。这对于与 related
结合使用特别有用,当需要同时发出多个错误时
use miette::{Diagnostic, Report, SourceSpan};
use thiserror::Error;
#[derive(Diagnostic, Debug, Error)]
#[error("oops")]
#[diagnostic()]
pub struct InnerError {
// Note: label but no source code
#[label]
err_span: SourceSpan,
}
#[derive(Diagnostic, Debug, Error)]
#[error("oops: multiple errors")]
#[diagnostic()]
pub struct MultiError {
// Note source code by no labels
#[source_code]
source_code: String,
// The source code above is used for these errors
#[related]
related: Vec<InnerError>,
}
fn do_something() -> Result<(), Vec<InnerError>> {
Err(vec![
InnerError {
err_span: (0..6).into(),
},
InnerError {
err_span: (7..11).into(),
},
])
}
fn main() -> miette::Result<()> {
do_something().map_err(|err_list| MultiError {
source_code: "source code".into(),
related: err_list,
})?;
Ok(())
}
...基于诊断的错误源。
当在字段上使用 #[source]
属性时,通常来自 thiserror
,并实现了 std::error::Error::source
方法。这在许多情况下都适用,但它是有损的:如果诊断的来源是另一个诊断,那么来源将被简单地视为一个 std::error::Error
。
尽管这对现有的 reporters 没有影响,因为它们现在不会使用这些信息,但可能需要这些信息的 API 将无法访问这些信息。
如果您认为这些信息对用户来说很重要,您可以在 #[source]
旁边使用 #[diagnostic_source]
。请注意,您可能不需要同时使用 两者
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[error("MyError")]
struct MyError {
#[source]
#[diagnostic_source]
the_cause: OtherError,
}
#[derive(Debug, Diagnostic, Error)]
#[error("OtherError")]
struct OtherError;
... 处理器选项
MietteHandler
是默认处理器,并且非常可定制。在大多数情况下,您可以使用 MietteHandlerOpts
来调整其行为,而不是回退到您自己的自定义处理器。
用法如下
miette::set_hook(Box::new(|_| {
Box::new(
miette::MietteHandlerOpts::new()
.terminal_links(true)
.unicode(false)
.context_lines(3)
.tab_width(4)
.break_words(true)
.build(),
)
}))
有关您可以定制的详细信息,请参阅 MietteHandlerOpts
的文档!
... 动态诊断
如果您...
- ...不知道所有可能的错误
- ...如果需要序列化和反序列化错误,则可能希望使用
miette!
、diagnostic!
宏或直接使用MietteDiagnostic
来动态创建诊断。
let source = "2 + 2 * 2 = 8".to_string();
let report = miette!(
labels = vec![
LabeledSpan::at(12..13, "this should be 6"),
],
help = "'*' has greater precedence than '+'",
"Wrong answer"
).with_source_code(source);
println!("{:?}", report)
... 语法高亮
miette
可以配置以突出显示源代码片段中的语法。
要使用内置的突出显示功能,必须启用 syntect-highlighter
包特性。当此特性启用时,miette
将自动使用 syntect
包来突出显示你的 Diagnostic
的 #[source_code]
字段。
使用 syntect
进行语法检测是通过检查 SpanContents
特性上的2个方法来处理的。
- language() - 提供语言的名称作为字符串。例如,
"Rust"
将指示 Rust 语法高亮。您可以通过with_language
方法设置由NamedSource
产生的SpanContents
的语言。 - name() - 如果没有显式设置语言,则假设名称包含文件名或文件路径。高亮器将在名称的末尾检查文件扩展名并尝试从中猜测语法。
如果您想使用自定义高亮器,可以通过调用 with_syntax_highlighting
方法向 MietteHandlerOpts
提供自定义的 Highlighter
特性实现。有关更多详细信息,请参阅 highlighters
模块文档。
... 标签集合
当标签数量未知时,您可以使用一组 SourceSpan
(或任何可以转换为 SourceSpan
的类型)。为此,将 collection
参数添加到 label
并使用任何可以迭代的类型作为字段。
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
struct MyError {
#[label("main issue")]
primary_span: SourceSpan,
#[label(collection, "related to this")]
other_spans: Vec<Range<usize>>,
}
let report: miette::Report = MyError {
primary_span: (6, 9).into(),
other_spans: vec![19..26, 30..41],
}.into();
println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
如果想要为不同的标签使用不同的文本,则集合也可以是 LabeledSpan
。没有文本的标签将使用 label
属性中的文本。
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
struct MyError {
#[label("main issue")]
primary_span: SourceSpan,
#[label(collection, "related to this")]
other_spans: Vec<LabeledSpan>, // LabeledSpan
}
let report: miette::Report = MyError {
primary_span: (6, 9).into(),
other_spans: vec![
LabeledSpan::new(None, 19, 7), // Use default text `related to this`
LabeledSpan::new(Some("and also this".to_string()), 30, 11), // Use specific text
],
}.into();
println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
MSRV
此包需要 rustc 1.70.0 或更高版本。
鸣谢
miette
并非空穴来风。它对各种其他项目和它们的作者给予了巨大的信用。
anyhow
和color-eyre
:这两个极具影响力的错误处理库推动了应用级错误处理和错误报告的体验。《miette》的Report
类型是对它们Report
类型的一个非常粗略的尝试。- 用于设置库级错误定义标准的
thiserror
,以及作为miette
的 derive 宏的灵感来源。 rustc
和 @estebank 对于他们在编译器诊断方面的一流工作。- 为使诊断看起来更美观,
ariadne
发挥了推动作用!
许可证
miette
在 Apache 许可证 2.0 下发布给 Rust 社区。
它还包括从 eyre
和 thiserror
(同样在 Apache 许可证下)以及从 ariadne
(MIT 许可证)中获取的代码。一些代码来自 ariadne
。
依赖项
~0.6–11MB
~118K SLoC