• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alorel / macroific-rs / 11197184821

05 Oct 2024 11:43PM CUT coverage: 81.468%. First build
11197184821

Pull #14

github

web-flow
Merge c8342d59b into 697e66c3a
Pull Request #14: v2

430 of 476 new or added lines in 16 files covered. (90.34%)

1099 of 1349 relevant lines covered (81.47%)

46.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.6
/modules/macroific-core/src/extract_fields.rs
1
//! Utilities for extracting specific types of fields.
2

3
use proc_macro2::Span;
4
use sealed::sealed;
5
use syn::punctuated::Punctuated;
6
use syn::spanned::Spanned;
7
use syn::{
8
    Data, DataEnum, DataStruct, DataUnion, Error, Field, Fields, FieldsNamed, FieldsUnnamed, Token,
9
};
10

11
type PunctuatedFields = Punctuated<Field, Token![,]>;
12

13
/// Convert this rejection to/into a [`syn::Error`].
14
#[sealed]
15
pub trait ToSynError {
16
    /// Convert this rejection to a [`syn::Error`].
17
    fn to_syn_err(&self) -> Error;
18

19
    /// Convert this rejection into a [`syn::Error`].
20
    #[inline]
21
    fn into_syn_err(self) -> Error
12✔
22
    where
12✔
23
        Self: Sized,
12✔
24
    {
12✔
25
        self.to_syn_err()
12✔
26
    }
12✔
27
}
28

29
/// [`Data`] extensions.
30
#[sealed]
31
pub trait DataExtractExt {
32
    /// Extract a union from a [`DeriveInput`](syn::DeriveInput)'s data.
33
    ///
34
    /// # Errors
35
    /// If there's a container mismatch.
36
    ///
37
    /// # Example
38
    ///
39
    /// ```
40
    /// # use macroific_core::extract_fields::*;
1✔
41
    /// # use syn::{DeriveInput, parse_quote};
42
    ///
43
    /// let input: DeriveInput = parse_quote!(struct MyStruct;);
44
    /// let err: syn::Error = input.data.extract_union().unwrap_err().into();
1✔
45
    /// assert_eq!(err.to_string(), "Only unions supported");
1✔
46
    ///
1✔
47
    /// let input: DeriveInput = parse_quote!(union MyUnion { foo: u8 });
48
    /// input.data.extract_union().unwrap(); // Ok
1✔
49
    /// ```
1✔
50
    fn extract_union(self) -> Result<DataUnion, Rejection<DataStruct, DataEnum>>;
1✔
51

52
    /// Extract a struct from a [`DeriveInput`](syn::DeriveInput)'s data.
53
    ///
54
    /// # Errors
55
    /// If there's a container mismatch.
56
    ///
57
    /// # Example
58
    ///
59
    /// ```
60
    /// # use macroific_core::extract_fields::*;
1✔
61
    /// # use syn::{DeriveInput, parse_quote};
62
    ///
63
    /// let input: DeriveInput = parse_quote!(enum MyEnum { Foo });
64
    /// let err: syn::Error = input.data.extract_struct().unwrap_err().into();
1✔
65
    /// assert_eq!(err.to_string(), "Only structs supported");
1✔
66
    ///
1✔
67
    /// let input: DeriveInput = parse_quote!(struct MyStruct;);
68
    /// input.data.extract_struct().unwrap(); // Ok
1✔
69
    /// ```
1✔
70
    fn extract_struct(self) -> Result<DataStruct, Rejection<DataEnum, DataUnion>>;
1✔
71

72
    /// Extract an enum from a [`DeriveInput`](syn::DeriveInput)'s data.
73
    ///
74
    /// # Errors
75
    /// If there's a container mismatch.
76
    ///
77
    /// ```
78
    /// # use macroific_core::extract_fields::*;
1✔
79
    /// # use syn::{DeriveInput, parse_quote};
80
    ///
81
    /// let input: DeriveInput = parse_quote!(struct MyStruct;);
82
    /// let err: syn::Error = input.data.extract_enum().unwrap_err().into();
1✔
83
    /// assert_eq!(err.to_string(), "Only enums supported");
1✔
84
    ///
1✔
85
    /// let input: DeriveInput = parse_quote!(enum MyEnum { Foo });
86
    /// input.data.extract_enum().unwrap(); // Ok
1✔
87
    /// ```
1✔
88
    fn extract_enum(self) -> Result<DataEnum, Rejection<DataStruct, DataUnion>>;
1✔
89

90
    /// [`extract_struct`](crate::extract_fields::DataExtractExt::extract_struct) and then
91
    /// [`extract_any_fields`](FieldsExtractExt::extract_any_fields).
92
    ///
93
    /// # Errors
94
    /// If there's a container mismatch.
95
    ///
96
    /// # Example
97
    ///
98
    /// ```
99
    /// # use macroific_core::extract_fields::*;
1✔
100
    /// # use syn::{DeriveInput, Fields, parse_quote};
101
    /// #
102
    /// let input: DeriveInput = parse_quote!(struct Foo { foo: u8 });
103
    /// assert!(input.data.extract_struct_fields().is_ok());
1✔
104
    ///
1✔
105
    /// let input: DeriveInput = parse_quote!(struct Foo;);
106
    /// let err: syn::Error = input.data.extract_struct_fields().unwrap_err().into();
1✔
107
    /// assert_eq!(err.to_string(), "Unit structs not supported");
1✔
108
    ///
1✔
109
    /// let input: DeriveInput = parse_quote!(enum Foo { A(u8) });
110
    /// let err: syn::Error = input.data.extract_struct_fields().unwrap_err().into();
1✔
111
    /// assert_eq!(err.to_string(), "Only structs supported");
1✔
112
    /// ```
1✔
113
    fn extract_struct_fields(self) -> syn::Result<PunctuatedFields>
3✔
114
    where
3✔
115
        Self: Sized,
3✔
116
    {
3✔
117
        self.extract_struct()?.fields.extract_any_fields()
3✔
118
    }
3✔
119

120
    /// [`extract_struct`](DataExtractExt::extract_struct) and then
121
    /// [`extract_named_fields`](FieldsExtractExt::extract_named_fields).
122
    ///
123
    /// # Errors
124
    /// If there's a container mismatch.
125
    ///
126
    /// # Example
127
    ///
128
    /// ```
129
    /// # use macroific_core::extract_fields::*;
1✔
130
    /// # use syn::{DeriveInput, Fields, parse_quote};
131
    /// #
132
    /// let input: DeriveInput = parse_quote!(struct Foo { foo: u8 });
133
    /// assert!(input.data.extract_struct_named().is_ok());
1✔
134
    ///
1✔
135
    /// let input: DeriveInput = parse_quote!(struct Foo(u8););
136
    /// let err: syn::Error = input.data.extract_struct_named().unwrap_err().into();
1✔
137
    /// assert_eq!(err.to_string(), "Only named fields supported");
1✔
138
    ///
1✔
139
    /// let input: DeriveInput = parse_quote!(enum Foo { A(u8) });
140
    /// let err: syn::Error = input.data.extract_struct_named().unwrap_err().into();
1✔
141
    /// assert_eq!(err.to_string(), "Only structs supported");
1✔
142
    /// ```
1✔
143
    fn extract_struct_named(self) -> syn::Result<PunctuatedFields>
3✔
144
    where
3✔
145
        Self: Sized,
3✔
146
    {
3✔
147
        self.extract_struct()
3✔
148
            .map_err(Error::from)?
3✔
149
            .fields
150
            .extract_named_fields()
2✔
151
            .map_err(Error::from)
2✔
152
    }
3✔
153

154
    /// [`extract_struct`](DataExtractExt::extract_struct) and then
155
    /// [`extract_unnamed_fields`](FieldsExtractExt::extract_unnamed_fields).
156
    ///
157
    /// # Errors
158
    /// If there's a container mismatch.
159
    ///
160
    /// # Example
161
    ///
162
    /// ```
163
    /// # use macroific_core::extract_fields::*;
1✔
164
    /// # use syn::{DeriveInput, Fields, parse_quote};
165
    /// #
166
    /// let input: DeriveInput = parse_quote!(struct Foo(u8););
167
    /// assert!(input.data.extract_struct_unnamed().is_ok());
1✔
168
    ///
1✔
169
    /// let input: DeriveInput = parse_quote!(struct Foo { foo: u8 });
170
    /// let err: syn::Error = input.data.extract_struct_unnamed().unwrap_err().into();
1✔
171
    /// assert_eq!(err.to_string(), "Only unnamed fields supported");
1✔
172
    ///
1✔
173
    /// let input: DeriveInput = parse_quote!(enum Foo { A(u8) });
174
    /// let err: syn::Error = input.data.extract_struct_unnamed().unwrap_err().into();
1✔
175
    /// assert_eq!(err.to_string(), "Only structs supported");
1✔
176
    /// ```
1✔
177
    fn extract_struct_unnamed(self) -> syn::Result<PunctuatedFields>
3✔
178
    where
3✔
179
        Self: Sized,
3✔
180
    {
3✔
181
        self.extract_struct()
3✔
182
            .map_err(Error::from)?
3✔
183
            .fields
184
            .extract_unnamed_fields()
2✔
185
            .map_err(Error::from)
2✔
186
    }
3✔
187
}
188

189
#[sealed]
190
impl DataExtractExt for Data {
191
    fn extract_union(self) -> Result<DataUnion, Rejection<DataStruct, DataEnum>> {
2✔
192
        match self {
2✔
193
            Data::Struct(data) => Err(Rejection::A(data)),
1✔
194
            Data::Enum(data) => Err(Rejection::B(data)),
×
195
            Data::Union(data) => Ok(data),
1✔
196
        }
197
    }
2✔
198

199
    fn extract_struct(self) -> Result<DataStruct, Rejection<DataEnum, DataUnion>> {
12✔
200
        match self {
12✔
201
            Data::Struct(data) => Ok(data),
8✔
202
            Data::Enum(data) => Err(Rejection::A(data)),
4✔
203
            Data::Union(data) => Err(Rejection::B(data)),
×
204
        }
205
    }
12✔
206

207
    fn extract_enum(self) -> Result<DataEnum, Rejection<DataStruct, DataUnion>> {
2✔
208
        match self {
2✔
209
            Data::Struct(data) => Err(Rejection::A(data)),
1✔
210
            Data::Enum(data) => Ok(data),
1✔
211
            Data::Union(data) => Err(Rejection::B(data)),
×
212
        }
213
    }
2✔
214
}
215

216
/// [`Fields`] extensions
217
#[sealed]
218
pub trait FieldsExtractExt {
219
    /// Extract named fields from [`Fields`]. `()` is returned for unit structs.
220
    ///
221
    /// # Errors
222
    /// If the fields are unnamed, `Err(Rejection::A)` is returned. If the fields are unit,
223
    /// `Err(Rejection::B)` is returned.
224
    ///
225
    /// # Example
226
    ///
227
    /// ```
228
    /// # use macroific_core::extract_fields::*;
1✔
229
    /// # use syn::{Fields, parse_quote};
230
    /// #
231
    /// let input = Fields::Unnamed(parse_quote!((u8)));
232
    /// let err: syn::Error = input.extract_named_fields().unwrap_err().into();
1✔
233
    ///
1✔
234
    /// assert_eq!(err.to_string(), "Only named fields supported");
1✔
235
    ///
1✔
236
    /// let input = Fields::Named(parse_quote!({ bar: u8 }));
237
    /// input.extract_named_fields().unwrap(); // Ok
1✔
238
    /// ```
1✔
239
    fn extract_named_fields(self) -> Result<PunctuatedFields, Rejection<FieldsUnnamed, ()>>;
1✔
240

241
    /// Extract unnamed fields from [`Fields`]. `()` is returned for unit structs.
242
    ///
243
    /// # Errors
244
    /// If the fields are named, `Err(Rejection::A)` is returned. If the fields are unit,
245
    /// `Err(Rejection::B)` is returned.
246
    ///
247
    /// # Example
248
    ///
249
    /// ```
250
    /// # use macroific_core::extract_fields::*;
1✔
251
    /// # use syn::{Fields, parse_quote};
252
    /// #
253
    /// let input = Fields::Named(parse_quote!({ bar: u8 }));
254
    /// let err: syn::Error = input.extract_unnamed_fields().unwrap_err().into();
1✔
255
    /// assert_eq!(err.to_string(), "Only unnamed fields supported");
1✔
256
    ///
1✔
257
    /// let input = Fields::Unnamed(parse_quote!((u8)));
258
    /// input.extract_unnamed_fields().unwrap(); // Ok
1✔
259
    /// ```
1✔
260
    fn extract_unnamed_fields(self) -> Result<PunctuatedFields, Rejection<FieldsNamed, ()>>;
1✔
261

262
    /// Extract named or unnamed fields
263
    ///
264
    /// # Errors
265
    /// If it's a unit struct
266
    ///
267
    /// # Example
268
    ///
269
    /// ```
270
    /// # use macroific_core::extract_fields::*;
1✔
271
    /// # use syn::Fields;
272
    /// #
273
    /// let err: syn::Error = Fields::Unit.extract_any_fields().unwrap_err().into();
274
    /// assert_eq!(err.to_string(), "Unit structs not supported");
1✔
275
    /// ```
1✔
276
    fn extract_any_fields(self) -> syn::Result<PunctuatedFields>
3✔
277
    where
3✔
278
        Self: Sized,
3✔
279
    {
3✔
280
        match self.extract_named_fields() {
3✔
281
            Ok(fields) => Ok(fields),
1✔
282
            Err(Rejection::A(fields)) => Ok(fields.unnamed),
×
283
            Err(e) => Err(Error::new(
2✔
284
                e.into_syn_err().span(),
2✔
285
                "Unit structs not supported",
2✔
286
            )),
2✔
287
        }
288
    }
3✔
289
}
290

291
#[sealed]
292
impl FieldsExtractExt for Fields {
293
    /// Extract named fields from [`Fields`]. `()` is returned for unit structs.
294
    fn extract_named_fields(self) -> Result<PunctuatedFields, Rejection<FieldsUnnamed, ()>> {
7✔
295
        match self {
7✔
296
            Fields::Named(fields) => Ok(fields.named),
3✔
297
            Fields::Unnamed(fields) => Err(Rejection::A(fields)),
2✔
298
            Fields::Unit => Err(Rejection::B(())),
2✔
299
        }
300
    }
7✔
301

302
    /// Extract unnamed fields from [`Fields`]. `()` is returned for unit structs.
303
    fn extract_unnamed_fields(self) -> Result<PunctuatedFields, Rejection<FieldsNamed, ()>> {
4✔
304
        match self {
4✔
305
            Fields::Named(fields) => Err(Rejection::A(fields)),
2✔
306
            Fields::Unnamed(fields) => Ok(fields.unnamed),
2✔
307
            Fields::Unit => Err(Rejection::B(())),
×
308
        }
309
    }
4✔
310
}
311

312
/// One of two error states
313
#[allow(missing_docs)]
314
#[derive(Debug, Eq, PartialEq)]
315
pub enum Rejection<A, B> {
316
    A(A),
317
    B(B),
318
}
319

320
impl<A, B> From<Rejection<A, B>> for Error
321
where
322
    Rejection<A, B>: ToSynError,
323
{
324
    #[inline]
325
    fn from(value: Rejection<A, B>) -> Self {
10✔
326
        value.into_syn_err()
10✔
327
    }
10✔
328
}
329

330
#[sealed]
331
impl ToSynError for Rejection<FieldsUnnamed, ()> {
332
    fn to_syn_err(&self) -> Error {
4✔
333
        Error::new(
4✔
334
            match *self {
4✔
335
                Self::A(ref f) => f.span(),
2✔
336
                Self::B(()) => Span::call_site(),
2✔
337
            },
338
            "Only named fields supported",
339
        )
340
    }
4✔
341
}
342

343
#[sealed]
344
impl ToSynError for Rejection<FieldsNamed, ()> {
345
    fn to_syn_err(&self) -> Error {
2✔
346
        Error::new(
2✔
347
            match *self {
2✔
348
                Self::A(ref f) => f.span(),
2✔
NEW
349
                Self::B(()) => Span::call_site(),
×
350
            },
351
            "Only unnamed fields supported",
352
        )
353
    }
2✔
354
}
355

356
macro_rules! impl_reject {
357
    ($msg: literal => [$a: ty => $p_a: ident, $b: ty => $p_b: ident]) => {
358
        #[sealed]
359
        impl ToSynError for Rejection<$a, $b> {
360
            fn to_syn_err(&self) -> Error {
6✔
361
                Error::new(
6✔
362
                    match self {
6✔
363
                        Self::A(v) => v.$p_a.span(),
6✔
364
                        Self::B(v) => v.$p_b.span(),
×
365
                    },
366
                    $msg,
367
                )
368
            }
6✔
369
        }
370
    };
371
}
372

373
impl_reject!("Only structs supported" => [DataEnum => enum_token, DataUnion => union_token]);
374
impl_reject!("Only enums supported" => [DataStruct => struct_token, DataUnion => union_token]);
375
impl_reject!("Only unions supported" => [DataStruct => struct_token, DataEnum => enum_token]);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc