miette/tests/derive.rs

608 lines
14 KiB
Rust

use miette::{Diagnostic, Report, Severity, SourceSpan};
use thiserror::Error;
#[test]
fn related() {
#[derive(Error, Debug, Diagnostic)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
struct Foo {
#[related]
related: Vec<Baz>,
}
#[derive(Error, Debug, Diagnostic)]
enum Bar {
#[error("variant1")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
Bad {
#[related]
related: Vec<Baz>,
},
#[error("variant2")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
LessBad(#[related] Vec<Baz>),
}
#[derive(Error, Debug, Diagnostic)]
#[error("welp2")]
struct Baz;
}
#[test]
fn related_report() {
#[derive(Error, Debug, Diagnostic)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
struct Foo {
#[related]
related: Vec<Report>,
}
}
#[test]
fn basic_struct() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(
code = "foo::bar::baz",
severity = "Error",
help = "try doing it better"
)]
struct Foo;
assert_eq!("foo::bar::baz".to_string(), Foo.code().unwrap().to_string());
assert_eq!(Some(Severity::Error), Foo.severity());
assert_eq!(
"try doing it better".to_string(),
Foo.help().unwrap().to_string()
);
}
#[test]
fn basic_enum() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum Foo {
#[diagnostic(
code = "foo::x",
severity = "Warning",
help = "Try using Foo::Y instead"
)]
X,
#[diagnostic(code = "foo::y")]
Y(usize),
#[diagnostic(code = "foo::z")]
Z { prop: String },
}
assert_eq!("foo::x".to_string(), Foo::X.code().unwrap().to_string());
assert_eq!("foo::y".to_string(), Foo::Y(1).code().unwrap().to_string());
assert_eq!(
"foo::z".to_string(),
Foo::Z { prop: "bar".into() }.code().unwrap().to_string()
);
assert_eq!(Some(Severity::Warning), Foo::X.severity());
assert_eq!(None, Foo::Y(1).severity());
}
#[test]
fn paren_code() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code("foo::bar::baz"))]
struct FooStruct;
assert_eq!(
"foo::bar::baz".to_string(),
FooStruct.code().unwrap().to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code("foo::x"))]
X,
}
assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
}
#[test]
fn path_code() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
struct FooStruct;
assert_eq!(
"foo::bar::baz".to_string(),
FooStruct.code().unwrap().to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code(foo::x))]
X,
}
assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
}
#[test]
fn path_severity() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), severity("warning"))]
struct FooStruct;
assert_eq!(Some(Severity::Warning), FooStruct.severity());
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code(foo::x), severity(Warning))]
X,
}
assert_eq!(Some(Severity::Warning), FooEnum::X.severity());
}
#[test]
fn list_help() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), help("try doing it better"))]
struct FooStruct;
assert_eq!(
"try doing it better".to_string(),
FooStruct.help().unwrap().to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code(foo::x), help("try doing it better"))]
X,
}
assert_eq!(
"try doing it better".to_string(),
FooEnum::X.help().unwrap().to_string()
);
}
#[test]
fn fmt_help() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), help("{} x {0} x {:?}", 1, "2"))]
struct FooStruct(String);
assert_eq!(
"1 x hello x \"2\"".to_string(),
FooStruct("hello".into()).help().unwrap().to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), help("{} x {my_field} x {:?}", 1, "2"))]
struct BarStruct {
my_field: String,
}
assert_eq!(
"1 x hello x \"2\"".to_string(),
BarStruct {
my_field: "hello".into()
}
.help()
.unwrap()
.to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code(foo::x), help("{} x {0} x {:?}", 1, "2"))]
X(String),
#[diagnostic(code(foo::x), help("{} x {len} x {:?}", 1, "2"))]
Y { len: usize },
#[diagnostic(code(foo::x), help("{} x {self:?} x {:?}", 1, "2"))]
Z,
}
assert_eq!(
"1 x bar x \"2\"".to_string(),
FooEnum::X("bar".into()).help().unwrap().to_string()
);
assert_eq!(
"1 x 10 x \"2\"".to_string(),
FooEnum::Y { len: 10 }.help().unwrap().to_string()
);
assert_eq!(
"1 x Z x \"2\"".to_string(),
FooEnum::Z.help().unwrap().to_string()
);
}
#[test]
fn help_field() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic()]
struct Foo {
#[help]
do_this: Option<String>,
}
assert_eq!(
"x".to_string(),
Foo {
do_this: Some("x".into())
}
.help()
.unwrap()
.to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic()]
enum Bar {
A(#[help] Option<String>),
B {
#[help]
do_this: Option<String>,
},
}
assert_eq!(
"x".to_string(),
Bar::A(Some("x".into())).help().unwrap().to_string()
);
assert_eq!(
"x".to_string(),
Bar::B {
do_this: Some("x".into())
}
.help()
.unwrap()
.to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic()]
struct Baz(#[help] Option<String>);
assert_eq!(
"x".to_string(),
Baz(Some("x".into())).help().unwrap().to_string()
);
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic()]
struct Quux(#[help] String);
assert_eq!(
"x".to_string(),
Quux("x".into()).help().unwrap().to_string()
);
}
#[test]
fn test_snippet_named_struct() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
struct Foo {
#[source_code]
src: String,
#[label("var 1")]
var1: SourceSpan,
#[label = "var 2"]
// Anything that's Clone + Into<SourceSpan> can be used here.
var2: (usize, usize),
#[label]
var3: (usize, usize),
#[label(optional, "var 4")]
var4: Option<(usize, usize)>,
#[label(optional)]
var5: Option<(usize, usize)>,
}
}
#[test]
fn test_snippet_unnamed_struct() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
struct Foo(
#[source_code] String,
#[label("{0}")] SourceSpan,
#[label = "idk"] SourceSpan,
#[label] SourceSpan,
#[label(optional, "foo")] Option<SourceSpan>,
#[label(optional)] Option<SourceSpan>,
);
}
#[test]
fn test_snippet_enum() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[allow(dead_code)]
enum Foo {
#[diagnostic(code(foo::a))]
A {
#[source_code]
src: String,
msg: String,
#[label("hi this is where the thing went wrong ({msg})")]
var0: SourceSpan,
#[label = "blorp"]
var1: SourceSpan,
#[label]
var2: SourceSpan,
#[label(optional, "var 3")]
var3: Option<(usize, usize)>,
#[label(optional)]
var4: Option<(usize, usize)>,
},
#[diagnostic(code(foo::b))]
B(
#[source_code] String,
String,
#[label("{1}")] SourceSpan,
#[label = "blorp"] SourceSpan,
#[label] SourceSpan,
#[label(optional, "foo")] Option<SourceSpan>,
#[label(optional)] Option<SourceSpan>,
),
}
}
#[test]
fn url_basic() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), url("https://example.com/foo/bar"))]
struct Foo {}
assert_eq!(
"https://example.com/foo/bar".to_string(),
Foo {}.url().unwrap().to_string()
);
}
#[test]
fn url_docsrs() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), url(docsrs))]
struct Foo {}
assert_eq!(
format!(
"https://docs.rs/miette/{}/miette/struct.Foo.html",
env!("CARGO_PKG_VERSION")
),
Foo {}.url().unwrap().to_string()
);
}
const SNIPPET_TEXT: &str = "hello from miette";
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(
// code not necessary.
// code(foo::bar::baz),
url("https://example.com"),
help("help"),
severity(Warning)
)]
struct ForwardsTo {
#[source_code]
src: String,
#[label("highlight text")]
label: miette::SourceSpan,
}
impl ForwardsTo {
fn new() -> Self {
ForwardsTo {
src: SNIPPET_TEXT.into(),
label: SourceSpan::new(11.into(), 6.into()),
}
}
}
fn check_all(diag: &impl Diagnostic) {
// check Diagnostic impl forwards all these methods
assert_eq!(diag.code().as_ref().map(|x| x.to_string()), None);
assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
assert_eq!(diag.help().unwrap().to_string(), "help");
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
}
#[test]
fn test_transparent_enum_unnamed() {
#[derive(Debug, Diagnostic, Error)]
enum Enum {
#[error("enum")]
#[diagnostic(transparent)]
FooVariant(#[from] ForwardsTo),
}
let variant = Enum::FooVariant(ForwardsTo::new());
check_all(&variant);
}
#[test]
fn test_transparent_enum_named() {
#[derive(Debug, Diagnostic, Error)]
enum Enum {
#[error("enum")]
#[diagnostic(transparent)]
FooVariant {
#[from]
single_field: ForwardsTo,
},
#[error("foo")]
#[diagnostic(code(foo::bar::bar_variant))]
BarVariant,
}
let variant = Enum::FooVariant {
single_field: ForwardsTo::new(),
};
check_all(&variant);
let bar_variant = Enum::BarVariant;
assert_eq!(
bar_variant.code().unwrap().to_string(),
"foo::bar::bar_variant"
);
}
#[test]
fn test_transparent_struct_named() {
#[derive(Debug, Diagnostic, Error)]
#[error(transparent)]
#[diagnostic(transparent)]
struct Struct {
#[from]
single_field: ForwardsTo,
}
// Also check the From impl here
let variant: Struct = ForwardsTo::new().into();
check_all(&variant);
}
#[test]
fn test_transparent_struct_unnamed() {
#[derive(Debug, Diagnostic, Error)]
#[error(transparent)]
#[diagnostic(transparent)]
struct Struct(#[from] ForwardsTo);
let variant = Struct(ForwardsTo::new());
check_all(&variant);
}
#[test]
fn test_forward_struct_named() {
#[derive(Debug, Diagnostic, Error)]
#[error("display")]
#[diagnostic(
code(foo::bar::overridden),
severity(Advice),
help("{help}"),
forward(span)
)]
struct Struct {
span: ForwardsTo,
help: &'static str,
}
// Also check the From impl here
let diag = Struct {
span: ForwardsTo::new(),
help: "overridden help please",
};
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
assert_eq!(diag.help().unwrap().to_string(), "overridden help please");
assert_eq!(diag.severity(), Some(Severity::Advice));
}
#[test]
fn test_forward_struct_unnamed() {
#[derive(Debug, Diagnostic, Error)]
#[error("display")]
#[diagnostic(code(foo::bar::overridden), url("{1}"), forward(0))]
struct Struct(ForwardsTo, &'static str);
// Also check the From impl here
let diag = Struct(ForwardsTo::new(), "url here");
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
assert_eq!(diag.url().unwrap().to_string(), "url here");
}
#[test]
fn test_forward_enum_named() {
#[derive(Debug, Diagnostic, Error)]
enum Enum {
#[error("help: {help_text}")]
#[diagnostic(code(foo::bar::overridden), help("{help_text}"), forward(span))]
Variant {
span: ForwardsTo,
help_text: &'static str,
},
}
// Also check the From impl here
let variant: Enum = Enum::Variant {
span: ForwardsTo::new(),
help_text: "overridden help please",
};
assert_eq!(variant.code().unwrap().to_string(), "foo::bar::overridden");
assert_eq!(
variant.help().unwrap().to_string(),
"overridden help please"
);
}
#[test]
fn test_forward_enum_unnamed() {
#[derive(Debug, Diagnostic, Error)]
enum ForwardEnumUnnamed {
#[error("help: {1}")]
#[diagnostic(code(foo::bar::overridden), help("{1}"), forward(0))]
Variant(ForwardsTo, &'static str),
}
// Also check the From impl here
let variant = ForwardEnumUnnamed::Variant(ForwardsTo::new(), "overridden help please");
assert_eq!(variant.code().unwrap().to_string(), "foo::bar::overridden");
assert_eq!(
variant.help().unwrap().to_string(),
"overridden help please"
);
}
#[test]
fn test_unit_struct_display() {
#[derive(Debug, Diagnostic, Error)]
#[error("unit only")]
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
struct UnitOnly;
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help")
}
#[test]
fn test_unit_enum_display() {
#[derive(Debug, Diagnostic, Error)]
enum Enum {
#[error("unit only")]
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
UnitVariant,
}
assert_eq!(
Enum::UnitVariant.help().unwrap().to_string(),
"hello from unit help"
)
}