use facet_showcase::{Language, ShowcaseRunner};
use std::process::Command;
fn main() {
let mut runner = ShowcaseRunner::new("Derive Macro Diagnostics")
.slug("derive-diagnostics")
.language(Language::Rust);
runner.header();
runner.intro(
"The `#[derive(Facet)]` macro provides helpful compile-time error messages \
when attributes are used incorrectly. This showcase demonstrates the various \
error scenarios and their diagnostics.",
);
runner.section("Representation Errors");
scenario_repr_c_rust_conflict(&mut runner);
scenario_repr_c_transparent_conflict(&mut runner);
scenario_repr_transparent_primitive_conflict(&mut runner);
scenario_repr_multiple_primitives(&mut runner);
scenario_repr_unknown_token(&mut runner);
scenario_repr_multiple_attributes(&mut runner);
runner.section("Rename Errors");
scenario_unknown_rename_all_rule(&mut runner);
runner.footer();
}
fn compile_snippet(code: &str) -> String {
use std::fs;
use std::path::Path;
let test_dir = Path::new("/tmp/facet-derive-error-test");
let src_dir = test_dir.join("src");
fs::create_dir_all(&src_dir).unwrap();
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let facet_path = Path::new(manifest_dir);
fs::write(
test_dir.join("Cargo.toml"),
format!(
r#"[package]
name = "test"
version = "0.1.0"
edition = "2021"
[dependencies]
facet = {{ path = "{}" }}
"#,
facet_path.display(),
),
)
.unwrap();
fs::write(src_dir.join("main.rs"), code).unwrap();
let output = Command::new("cargo")
.args(["check", "--color=always"])
.current_dir(test_dir)
.env("CARGO_TERM_COLOR", "always")
.output()
.expect("Failed to run cargo check");
String::from_utf8_lossy(&output.stderr).to_string()
}
fn extract_error(output: &str) -> String {
let mut lines: Vec<&str> = Vec::new();
let mut in_error = false;
for line in output.lines() {
if line.contains("Compiling")
|| line.contains("Checking")
|| line.contains("Updating")
|| line.contains("Locking")
|| line.contains("Downloading")
|| line.contains("Downloaded")
{
continue;
}
if line.contains("error") {
in_error = true;
}
if in_error {
lines.push(line);
}
}
lines.join("\n")
}
fn scenario_repr_c_rust_conflict(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(C, Rust)]
enum Status {
Active,
Inactive,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Conflicting repr: C and Rust")
.description(
"Using both `#[repr(C)]` and `#[repr(Rust)]` is not allowed.\n\
Facet defers to rustc's E0566 error for this - no duplicate diagnostic.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_repr_c_transparent_conflict(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(C, transparent)]
struct Wrapper(u32);
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Conflicting repr: C and transparent")
.description(
"Combining `#[repr(C)]` with `#[repr(transparent)]` is not valid.\n\
Facet defers to rustc's E0692 error for this - no duplicate diagnostic.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_repr_transparent_primitive_conflict(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(transparent, u8)]
enum Status {
Active,
Inactive,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Conflicting repr: transparent and primitive")
.description(
"Using `#[repr(transparent)]` with a primitive type like `u8` is not allowed.\n\
Facet defers to rustc's E0692 error for this - no duplicate diagnostic.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_repr_multiple_primitives(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(u8, u16)]
enum Priority {
Low,
Medium,
High,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Multiple primitive types in repr")
.description(
"Specifying multiple primitive types in `#[repr(...)]` is not allowed.\n\
Facet defers to rustc's E0566 error for this - no duplicate diagnostic.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_repr_unknown_token(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(packed)]
struct Data {
a: u8,
b: u32,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Unsupported repr (facet-specific)")
.description(
"Using `#[repr(packed)]` is valid Rust, but facet doesn't support it.\n\
This is a facet-specific error with a helpful message.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_repr_multiple_attributes(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[repr(C)]
#[repr(u8)]
enum Status {
Active,
Inactive,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Multiple #[repr] attributes")
.description(
"Having multiple separate `#[repr(...)]` attributes triggers rustc's E0566.\n\
Facet defers to rustc for this - no duplicate diagnostic.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}
fn scenario_unknown_rename_all_rule(runner: &mut ShowcaseRunner) {
let code = r#"use facet::Facet;
#[derive(Facet)]
#[facet(rename_all = "SCREAMING_SNAKE")]
struct Config {
user_name: String,
max_retries: u32,
}
fn main() {}
"#;
let output = compile_snippet(code);
let error = extract_error(&output);
runner
.scenario("Unknown rename_all rule (facet-specific)")
.description(
"Using an unknown case convention in `rename_all` is a facet-specific error.\n\
Valid options: `camelCase`, `snake_case`, `kebab-case`, `PascalCase`, `SCREAMING_SNAKE_CASE`.",
)
.input(Language::Rust, code)
.compiler_error(&error)
.finish();
}