2个稳定版本
7.1.1 | 2024年8月7日 |
---|
#251 在 Rust模式
838 每月下载量
在 5 个crate(2直接)中使用
305KB
5.5K SLoC
这是对 miette 的修改版本,支持 no-std
。
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(())
}
... 基于 diagnostics 的错误源。
当在字段上使用 #[source]
属性时,这通常来自 thiserror
,并实现了一个 std::error::Error::source
方法。这在许多情况下都有效,但它是有损失的:如果诊断的来源是另一个诊断,那么来源将简单地被当作一个 std::error::Error
。
虽然这对现有的 reporters 没有影响,因为它们现在不使用这些信息,但可能希望使用这些信息的 API 将无法访问这些信息。
如果您认为这些信息对用户来说很重要,您可以使用 #[diagnostic_source]
与 #[source]
一起使用。请注意,您可能不需要使用 both。
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
在 Rust 社区中以 Apache license 2.0 许可证发布。
它还包括从 eyre
和一些来自 thiserror
(也处于 Apache License 许可下)的代码。一些代码来自 ariadne
,该代码采用 MIT 许可。
依赖项
~1–15MB
~145K SLoC