Skip to content

Commit 08f5c96

Browse files
authored
Merge pull request #20 from panicbit/17-support-skip_if-attribute
17 support skip if attribute
2 parents 2e0bca6 + 8338d70 commit 08f5c96

6 files changed

Lines changed: 174 additions & 30 deletions

File tree

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
Derive `Debug` with a custom format per field.
44

5-
# Usage
5+
# Example usage
66

7-
Here is a showcase of all possible field attributes:
7+
Here is a showcase of `custom_debug`s features:
88

99
```rust
1010
use custom_debug::Debug;
@@ -16,8 +16,10 @@ Here is a showcase of all possible field attributes:
1616
x: i32,
1717
#[debug(skip)]
1818
y: i32,
19-
#[debug(with = "hex_fmt")]
19+
#[debug(with = hex_fmt)]
2020
z: i32,
21+
#[debug(skip_if = Option::is_none)]
22+
label: Option<String>,
2123
}
2224

2325
fn hex_fmt<T: fmt::Debug>(n: &T, f: &mut fmt::Formatter) -> fmt::Result {
@@ -33,3 +35,21 @@ Foo {
3335
z: 0xAB
3436
}
3537
```
38+
39+
# Field attributes reference
40+
41+
Attributes within a section below are considered mutually exclusive.
42+
43+
## Skip attributes
44+
45+
| | |
46+
|-|-|
47+
| `skip` | Unconditionally skips a field. |
48+
| `skip_if = path::to::function` | Skips a field if `path::to::function(&field)` returns `true`. |
49+
50+
## Format attributes
51+
52+
| | |
53+
|-|-|
54+
| `format = "format string {}"` | Formats a field using a format string. Must contain a placeholder (`{}`) with modifiers of your choice. |
55+
| `with = path::to::formatter` | Formats a field using `path::to::formatter`. The required signature is `fn(&T, &mut std::fmt::Formatter) -> std::fmt::Result` where `T` is a type compatible with the field's type (i.e. the function can be generic and coercions apply). |

custom_debug_derive/src/field_attributes.rs

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,48 @@
1-
use std::cell::OnceCell;
2-
31
use darling::util::Flag;
42
use darling::FromMeta;
53
use syn::ExprPath;
64

75
#[derive(Default)]
86
pub struct FieldAttributes {
9-
pub skip: bool,
7+
pub skip_mode: SkipMode,
108
pub debug_format: DebugFormat,
119
}
1210

1311
impl FieldAttributes {
1412
fn new(internal: InternalFieldAttributes) -> darling::Result<Self> {
15-
let skip = internal.skip.is_present();
16-
let debug_format = OnceCell::new();
13+
let mut skip_mode = SkipMode::Default;
14+
let mut debug_format = DebugFormat::Default;
15+
16+
if internal.skip.is_present() {
17+
skip_mode = skip_mode.try_combine(SkipMode::Always)?;
18+
}
19+
20+
if let Some(skip_if) = internal.skip_if {
21+
skip_mode = skip_mode.try_combine(SkipMode::Condition(skip_if))?;
22+
}
1723

1824
if let Some(format) = internal.format {
19-
debug_format
20-
.set(DebugFormat::Format(format))
21-
.map_err(|_| conflicting_format_options_error())?;
25+
debug_format = debug_format.try_combine(DebugFormat::Format(format))?;
2226
}
2327

2428
if let Some(with) = internal.with {
25-
debug_format
26-
.set(DebugFormat::With(with))
27-
.map_err(|_| conflicting_format_options_error())?;
29+
debug_format = debug_format.try_combine(DebugFormat::With(with))?;
2830
}
2931

30-
let debug_format = debug_format.into_inner().unwrap_or(DebugFormat::Default);
31-
32-
Ok(Self { skip, debug_format })
32+
Ok(Self {
33+
skip_mode,
34+
debug_format,
35+
})
3336
}
3437

3538
pub fn try_combine(self, other: Self) -> darling::Result<Self> {
36-
let skip = self.skip || other.skip;
39+
let skip_mode = self.skip_mode.try_combine(other.skip_mode)?;
3740
let debug_format = self.debug_format.try_combine(other.debug_format)?;
3841

39-
Ok(Self { skip, debug_format })
42+
Ok(Self {
43+
skip_mode,
44+
debug_format,
45+
})
4046
}
4147
}
4248

@@ -80,13 +86,36 @@ impl DebugFormat {
8086
}
8187
}
8288

89+
#[derive(Default, PartialEq, Eq)]
90+
pub enum SkipMode {
91+
#[default]
92+
Default,
93+
Condition(ExprPath),
94+
Always,
95+
}
96+
97+
impl SkipMode {
98+
fn try_combine(self, other: Self) -> darling::Result<Self> {
99+
match (&self, &other) {
100+
(SkipMode::Default, _) => Ok(other),
101+
(_, SkipMode::Default) => Ok(self),
102+
_ => Err(conflicting_skip_options_error()),
103+
}
104+
}
105+
}
106+
83107
#[derive(FromMeta)]
84108
struct InternalFieldAttributes {
85109
skip: Flag,
110+
skip_if: Option<ExprPath>,
86111
format: Option<String>,
87112
with: Option<ExprPath>,
88113
}
89114

115+
fn conflicting_skip_options_error() -> darling::Error {
116+
darling::Error::custom("Conflicting skip options")
117+
}
118+
90119
fn conflicting_format_options_error() -> darling::Error {
91120
darling::Error::custom("Conflicting format options")
92121
}

custom_debug_derive/src/lib.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use quote::quote;
44
use syn::{Fields, Result};
55
use synstructure::{decl_derive, AddBounds, BindingInfo, Structure, VariantInfo};
66

7-
use crate::field_attributes::{DebugFormat, FieldAttributes};
8-
use crate::filter_ext::RetainExt;
7+
use crate::field_attributes::{DebugFormat, FieldAttributes, SkipMode};
98
use crate::result_into_stream_ext::ResultIntoStreamExt;
9+
use crate::retain_ext::RetainExt;
1010

1111
mod field_attributes;
12-
mod filter_ext;
1312
mod result_into_stream_ext;
13+
mod retain_ext;
1414
#[cfg(test)]
1515
mod tests;
1616

@@ -39,7 +39,7 @@ fn filter_out_skipped_fields(structure: &mut Structure) -> Result<()> {
3939
structure.try_retain(|binding| {
4040
let field_attributes = parse_field_attributes(binding)?;
4141

42-
Ok(!field_attributes.skip)
42+
Ok(field_attributes.skip_mode != SkipMode::Always)
4343
})?;
4444

4545
Ok(())
@@ -51,11 +51,27 @@ fn generate_match_arm_body(variant: &VariantInfo) -> Result<TokenStream> {
5151
Fields::Named(_) | Fields::Unit => quote! { debug_struct },
5252
Fields::Unnamed(_) => quote! { debug_tuple },
5353
};
54-
let debug_builder_calls = variant
55-
.bindings()
56-
.iter()
57-
.map(generate_debug_builder_call)
58-
.collect::<Result<Vec<_>>>()?;
54+
let mut debug_builder_calls = Vec::new();
55+
56+
for binding in variant.bindings() {
57+
let field_attributes = parse_field_attributes(binding)?;
58+
59+
let debug_builder_call = match &field_attributes.skip_mode {
60+
SkipMode::Default => generate_debug_builder_call(binding, &field_attributes)?,
61+
SkipMode::Condition(condition) => {
62+
let debug_builder_call = generate_debug_builder_call(binding, &field_attributes)?;
63+
64+
quote! {
65+
if (!#condition(#binding)) {
66+
#debug_builder_call
67+
}
68+
}
69+
}
70+
SkipMode::Always => quote! {},
71+
};
72+
73+
debug_builder_calls.push(debug_builder_call);
74+
}
5975

6076
Ok(quote! {
6177
let mut debug_builder = fmt.#debug_builder(#name);
@@ -66,8 +82,10 @@ fn generate_match_arm_body(variant: &VariantInfo) -> Result<TokenStream> {
6682
})
6783
}
6884

69-
fn generate_debug_builder_call(binding: &BindingInfo) -> Result<TokenStream> {
70-
let field_attributes = parse_field_attributes(binding)?;
85+
fn generate_debug_builder_call(
86+
binding: &BindingInfo,
87+
field_attributes: &FieldAttributes,
88+
) -> Result<TokenStream> {
7189
let format = generate_debug_impl(binding, &field_attributes.debug_format);
7290

7391
let debug_builder_call =

custom_debug_derive/src/tests.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,45 @@ fn test_skip() {
148148
}
149149
}
150150

151+
#[test]
152+
fn test_conditional_skip() {
153+
test_derive! {
154+
custom_debug_derive {
155+
struct Point {
156+
x: f32,
157+
#[debug(skip_if = Option::is_none)]
158+
y: Option<f32>,
159+
z: f32,
160+
}
161+
}
162+
163+
expands to {
164+
#[allow(non_upper_case_globals)]
165+
const _DERIVE_core_fmt_Debug_FOR_Point: () = {
166+
impl ::core::fmt::Debug for Point {
167+
fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
168+
match self {
169+
Point { x: ref __binding_0, y: ref __binding_1, z: ref __binding_2, } => {
170+
let mut debug_builder = fmt.debug_struct("Point");
171+
debug_builder.field("x", __binding_0);
172+
173+
if (!Option::is_none(__binding_1)) {
174+
debug_builder.field("y", __binding_1);
175+
}
176+
177+
debug_builder.field("z", __binding_2);
178+
debug_builder.finish()
179+
}
180+
}
181+
}
182+
}
183+
};
184+
}
185+
186+
no_build
187+
}
188+
}
189+
151190
#[test]
152191
fn test_enum() {
153192
test_derive! {

examples/conditional_skip.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![allow(clippy::disallowed_names)]
2+
use core::fmt;
3+
4+
use custom_debug::Debug;
5+
6+
#[derive(Debug)]
7+
struct Foo {
8+
x: i32,
9+
#[debug(
10+
skip_if = Option::is_none,
11+
with = strip_some,
12+
)]
13+
y: Option<i32>,
14+
z: i32,
15+
}
16+
17+
fn main() {
18+
let mut foo = Foo {
19+
x: 42,
20+
y: None,
21+
z: 171,
22+
};
23+
24+
println!("With `y = None`:");
25+
println!("{:#?}", foo);
26+
27+
foo.y = Some(123);
28+
println!("With `y = Some(123)`:");
29+
println!("{:#?}", foo);
30+
}
31+
32+
fn strip_some<T: fmt::Debug>(value: &Option<T>, f: &mut fmt::Formatter) -> fmt::Result {
33+
if let Some(value) = value {
34+
value.fmt(f)?;
35+
}
36+
37+
Ok(())
38+
}

0 commit comments

Comments
 (0)