Skip to content

Commit 1999406

Browse files
committed
test: add more tests and const generics documentation
Fill test coverage gaps identified during code review: - `trybuild`: verify error messages for missing Facet derive, non-Debug type params, and const generics (documenting the limitation) - `insta`: snapshot tests for exact Debug output format verification across structs, enums, tuple structs, and nested types - Bump `facet` dev-dep to 0.44, lower MSRV to 1.87.0
1 parent 8392ae0 commit 1999406

18 files changed

Lines changed: 346 additions & 2 deletions

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "safe-debug"
33
version = "0.1.1"
44
edition = "2024"
5-
rust-version = "1.90.0"
5+
rust-version = "1.87.0"
66
authors = ["C J Silverio <ceejceej@gmail.com>"]
77
description = "Derives std::fmt::Debug with automatic redaction for sensitive fields marked with #[facet(sensitive)]"
88
license = "MIT OR Apache-2.0"
@@ -27,7 +27,9 @@ quote = "1.0"
2727
facet-macros-parse = "0.31"
2828

2929
[dev-dependencies]
30-
facet = { version = "0.43.2", features = ["reflect"] }
30+
facet = { version = "0.44", features = ["reflect"] }
31+
insta = "1"
32+
trybuild = { version = "1", features = ["diff"] }
3133

3234
[[example]]
3335
name = "enum_example"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// Const generics are not supported.
2+
/// Both `Facet` and `SafeDebug` fail on const generic parameters.
3+
/// Facet's derive rejects `const N: usize` outright, and SafeDebug's
4+
/// string-based generics parser produces unparsable tokens.
5+
use facet::Facet;
6+
use safe_debug::SafeDebug;
7+
8+
#[derive(Facet, SafeDebug)]
9+
struct FixedBuffer<const N: usize> {
10+
#[facet(sensitive)]
11+
data: [u8; N],
12+
label: String,
13+
}
14+
15+
fn main() {
16+
let buf: FixedBuffer<4> = FixedBuffer {
17+
data: [1, 2, 3, 4],
18+
label: "test".to_string(),
19+
};
20+
println!("{:?}", buf);
21+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: unexpected `const` parameter declaration
2+
--> tests/compile_fail/const_generic.rs:9:20
3+
|
4+
9 | struct FixedBuffer<const N: usize> {
5+
| ^^^^^^^^^^^^^^ expected a `const` expression, not a parameter declaration
6+
7+
error: expected one of `#`, `{`, lifetime, or type, found keyword `const`
8+
--> tests/compile_fail/const_generic.rs:8:17
9+
|
10+
8 | #[derive(Facet, SafeDebug)]
11+
| ^^^^^^^^^ expected one of `#`, `{`, lifetime, or type
12+
|
13+
= note: this error originates in the derive macro `SafeDebug` (in Nightly builds, run with -Z macro-backtrace for more info)
14+
15+
error: proc-macro derive produced unparsable tokens
16+
--> tests/compile_fail/const_generic.rs:8:17
17+
|
18+
8 | #[derive(Facet, SafeDebug)]
19+
| ^^^^^^^^^
20+
21+
error[E0277]: `FixedBuffer<4>` doesn't implement `std::fmt::Debug`
22+
--> tests/compile_fail/const_generic.rs:20:22
23+
|
24+
20 | println!("{:?}", buf);
25+
| ---- ^^^ `FixedBuffer<4>` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
26+
| |
27+
| required by this formatting parameter
28+
|
29+
= help: the trait `std::fmt::Debug` is not implemented for `FixedBuffer<4>`
30+
= note: add `#[derive(Debug)]` to `FixedBuffer<4>` or manually `impl std::fmt::Debug for FixedBuffer<4>`
31+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
32+
help: consider annotating `FixedBuffer<4>` with `#[derive(Debug)]`
33+
|
34+
9 + #[derive(Debug)]
35+
10 | struct FixedBuffer<const N: usize> {
36+
|
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Deriving SafeDebug without Facet should produce a helpful error.
2+
use safe_debug::SafeDebug;
3+
4+
#[derive(SafeDebug)]
5+
struct MissingFacet {
6+
name: String,
7+
#[facet(sensitive)]
8+
secret: String,
9+
}
10+
11+
fn main() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: cannot find attribute `facet` in this scope
2+
--> tests/compile_fail/missing_facet_derive.rs:7:7
3+
|
4+
7 | #[facet(sensitive)]
5+
| ^^^^^
6+
|
7+
= note: `facet` is in scope, but it is a crate, not an attribute
8+
9+
error[E0599]: no associated item named `SHAPE` found for struct `MissingFacet` in the current scope
10+
--> tests/compile_fail/missing_facet_derive.rs:4:10
11+
|
12+
4 | #[derive(SafeDebug)]
13+
| ^^^^^^^^^ associated item not found in `MissingFacet`
14+
5 | struct MissingFacet {
15+
| ------------------- associated item `SHAPE` not found for this struct
16+
|
17+
= help: items from traits can only be used if the trait is implemented and in scope
18+
= note: the following trait defines an item `SHAPE`, perhaps you need to implement it:
19+
candidate #1: `Facet`
20+
= note: this error originates in the derive macro `SafeDebug` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// A generic type parameter that doesn't implement Debug should fail to compile.
2+
use facet::Facet;
3+
use safe_debug::SafeDebug;
4+
5+
struct NotDebug;
6+
7+
#[derive(Facet, SafeDebug)]
8+
struct Container<T> {
9+
value: T,
10+
}
11+
12+
fn main() {
13+
let c = Container { value: NotDebug };
14+
// This should fail because NotDebug doesn't implement Debug
15+
println!("{:?}", c);
16+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
error[E0277]: `NotDebug` doesn't implement `std::fmt::Debug`
2+
--> tests/compile_fail/non_debug_type_param.rs:15:22
3+
|
4+
15 | println!("{:?}", c);
5+
| ---- ^ `NotDebug` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
6+
| |
7+
| required by this formatting parameter
8+
|
9+
= help: the trait `std::fmt::Debug` is not implemented for `NotDebug`
10+
= note: add `#[derive(Debug)]` to `NotDebug` or manually `impl std::fmt::Debug for NotDebug`
11+
help: the trait `std::fmt::Debug` is implemented for `Container<T>`
12+
--> tests/compile_fail/non_debug_type_param.rs:7:17
13+
|
14+
7 | #[derive(Facet, SafeDebug)]
15+
| ^^^^^^^^^
16+
note: required for `Container<NotDebug>` to implement `std::fmt::Debug`
17+
--> tests/compile_fail/non_debug_type_param.rs:7:17
18+
|
19+
7 | #[derive(Facet, SafeDebug)]
20+
| ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
21+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the derive macro `SafeDebug` (in Nightly builds, run with -Z macro-backtrace for more info)
22+
help: consider annotating `NotDebug` with `#[derive(Debug)]`
23+
|
24+
5 + #[derive(Debug)]
25+
6 | struct NotDebug;
26+
|
27+
28+
error[E0277]: the trait bound `for<'__facet> NotDebug: Facet<'__facet>` is not satisfied
29+
--> tests/compile_fail/non_debug_type_param.rs:15:22
30+
|
31+
15 | println!("{:?}", c);
32+
| ---- ^ unsatisfied trait bound
33+
| |
34+
| required by this formatting parameter
35+
|
36+
help: the trait `for<'__facet> Facet<'__facet>` is not implemented for `NotDebug`
37+
--> tests/compile_fail/non_debug_type_param.rs:5:1
38+
|
39+
5 | struct NotDebug;
40+
| ^^^^^^^^^^^^^^^
41+
= help: the following other types implement trait `Facet<'facet>`:
42+
`&'a T` implements `Facet<'a>`
43+
`&'a mut T` implements `Facet<'a>`
44+
`()` implements `Facet<'_>`
45+
`(T0, T1)` implements `Facet<'a>`
46+
`(T0, T1, T2)` implements `Facet<'a>`
47+
`(T0, T1, T2, T3)` implements `Facet<'a>`
48+
`(T0,)` implements `Facet<'a>`
49+
`*const T` implements `Facet<'a>`
50+
and $N others
51+
note: required for `Container<NotDebug>` to implement `std::fmt::Debug`
52+
--> tests/compile_fail/non_debug_type_param.rs:7:17
53+
|
54+
7 | #[derive(Facet, SafeDebug)]
55+
| ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
56+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the derive macro `SafeDebug` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/compile_fail_tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[test]
2+
fn compile_fail() {
3+
let t = trybuild::TestCases::new();
4+
t.compile_fail("tests/compile_fail/*.rs");
5+
}

tests/snapshot_tests.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use facet::Facet;
2+
use safe_debug::SafeDebug;
3+
4+
// --- Type definitions for snapshot tests ---
5+
6+
#[derive(Facet, SafeDebug)]
7+
struct PatientRecord {
8+
id: String,
9+
name: String,
10+
#[facet(sensitive)]
11+
ssn: String,
12+
#[facet(sensitive)]
13+
medical_history: String,
14+
}
15+
16+
#[derive(Facet, SafeDebug)]
17+
struct TupleRecord(String, #[facet(sensitive)] String, i32);
18+
19+
#[derive(Facet, SafeDebug)]
20+
#[repr(C)]
21+
enum ApiResponse {
22+
Success { code: u16, data: String },
23+
Error {
24+
code: u16,
25+
#[facet(sensitive)]
26+
details: String,
27+
},
28+
Pending,
29+
Token(#[facet(sensitive)] String),
30+
}
31+
32+
#[derive(Facet, SafeDebug)]
33+
struct Inner {
34+
public_data: String,
35+
#[facet(sensitive)]
36+
secret_data: String,
37+
}
38+
39+
#[derive(Facet, SafeDebug)]
40+
struct Outer {
41+
id: u32,
42+
inner: Inner,
43+
}
44+
45+
// --- Snapshot tests ---
46+
47+
#[test]
48+
fn snapshot_named_struct() {
49+
let record = PatientRecord {
50+
id: "P-12345".to_string(),
51+
name: "Jane Doe".to_string(),
52+
ssn: "123-45-6789".to_string(),
53+
medical_history: "Chronic condition notes".to_string(),
54+
};
55+
insta::assert_snapshot!(format!("{:?}", record));
56+
}
57+
58+
#[test]
59+
fn snapshot_named_struct_pretty() {
60+
let record = PatientRecord {
61+
id: "P-12345".to_string(),
62+
name: "Jane Doe".to_string(),
63+
ssn: "123-45-6789".to_string(),
64+
medical_history: "Chronic condition notes".to_string(),
65+
};
66+
insta::assert_snapshot!(format!("{:#?}", record));
67+
}
68+
69+
#[test]
70+
fn snapshot_tuple_struct() {
71+
let record = TupleRecord("visible".to_string(), "secret-token".to_string(), 42);
72+
insta::assert_snapshot!(format!("{:?}", record));
73+
}
74+
75+
#[test]
76+
fn snapshot_enum_struct_variant() {
77+
let success = ApiResponse::Success {
78+
code: 200,
79+
data: "OK".to_string(),
80+
};
81+
insta::assert_snapshot!("enum_success", format!("{:?}", success));
82+
83+
let error = ApiResponse::Error {
84+
code: 500,
85+
details: "password leaked in stack trace".to_string(),
86+
};
87+
insta::assert_snapshot!("enum_error_redacted", format!("{:?}", error));
88+
}
89+
90+
#[test]
91+
fn snapshot_enum_unit_and_tuple_variants() {
92+
let pending = ApiResponse::Pending;
93+
insta::assert_snapshot!("enum_pending", format!("{:?}", pending));
94+
95+
let token = ApiResponse::Token("secret-bearer-token".to_string());
96+
insta::assert_snapshot!("enum_token_redacted", format!("{:?}", token));
97+
}
98+
99+
#[test]
100+
fn snapshot_nested_struct() {
101+
let data = Outer {
102+
id: 999,
103+
inner: Inner {
104+
public_data: "visible-info".to_string(),
105+
secret_data: "classified-payload".to_string(),
106+
},
107+
};
108+
insta::assert_snapshot!(format!("{:?}", data));
109+
}
110+
111+
#[test]
112+
fn snapshot_nested_struct_pretty() {
113+
let data = Outer {
114+
id: 999,
115+
inner: Inner {
116+
public_data: "visible-info".to_string(),
117+
secret_data: "classified-payload".to_string(),
118+
},
119+
};
120+
insta::assert_snapshot!(format!("{:#?}", data));
121+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: tests/snapshot_tests.rs
3+
expression: "format!(\"{:?}\", error)"
4+
---
5+
ApiResponse::Error { code: 500, details: "[REDACTED]" }

0 commit comments

Comments
 (0)