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

Alorel / macroific-rs / 11196013173

05 Oct 2024 08:14PM UTC coverage: 76.985%. First build
11196013173

Pull #14

github

web-flow
Merge 76c50db01 into de4fadcc0
Pull Request #14: v2

328 of 378 new or added lines in 14 files covered. (86.77%)

960 of 1247 relevant lines covered (76.98%)

50.23 hits per line

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

75.76
/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
6✔
22
    where
6✔
23
        Self: Sized,
6✔
24
    {
6✔
25
        self.to_syn_err()
6✔
26
    }
6✔
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
    fn extract_struct_fields(self) -> syn::Result<PunctuatedFields>
×
96
    where
×
97
        Self: Sized,
×
98
    {
×
99
        self.extract_struct()?.fields.extract_any_fields()
×
100
    }
×
101

102
    /// [`extract_struct`](DataExtractExt::extract_struct) and then
103
    /// [`extract_named_fields`](FieldsExtractExt::extract_named_fields).
104
    ///
105
    /// # Errors
106
    /// If there's a container mismatch.
107
    fn extract_struct_named(self) -> syn::Result<PunctuatedFields>
×
108
    where
×
109
        Self: Sized,
×
110
    {
×
111
        self.extract_struct()
×
NEW
112
            .map_err(Error::from)?
×
113
            .fields
114
            .extract_named_fields()
×
NEW
115
            .map_err(Error::from)
×
116
    }
×
117

118
    /// [`extract_struct`](DataExtractExt::extract_struct) and then
119
    /// [`extract_unnamed_fields`](FieldsExtractExt::extract_unnamed_fields).
120
    ///
121
    /// # Errors
122
    /// If there's a container mismatch.
123
    fn extract_struct_unnamed(self) -> syn::Result<PunctuatedFields>
×
124
    where
×
125
        Self: Sized,
×
126
    {
×
127
        self.extract_struct()
×
NEW
128
            .map_err(Error::from)?
×
129
            .fields
130
            .extract_unnamed_fields()
×
NEW
131
            .map_err(Error::from)
×
132
    }
×
133
}
134

135
#[sealed]
136
impl DataExtractExt for Data {
137
    fn extract_union(self) -> Result<DataUnion, Rejection<DataStruct, DataEnum>> {
2✔
138
        match self {
2✔
139
            Data::Struct(data) => Err(Rejection::A(data)),
1✔
140
            Data::Enum(data) => Err(Rejection::B(data)),
×
141
            Data::Union(data) => Ok(data),
1✔
142
        }
143
    }
2✔
144

145
    fn extract_struct(self) -> Result<DataStruct, Rejection<DataEnum, DataUnion>> {
3✔
146
        match self {
3✔
147
            Data::Struct(data) => Ok(data),
2✔
148
            Data::Enum(data) => Err(Rejection::A(data)),
1✔
149
            Data::Union(data) => Err(Rejection::B(data)),
×
150
        }
151
    }
3✔
152

153
    fn extract_enum(self) -> Result<DataEnum, Rejection<DataStruct, DataUnion>> {
2✔
154
        match self {
2✔
155
            Data::Struct(data) => Err(Rejection::A(data)),
1✔
156
            Data::Enum(data) => Ok(data),
1✔
157
            Data::Union(data) => Err(Rejection::B(data)),
×
158
        }
159
    }
2✔
160
}
161

162
/// [`Fields`] extensions
163
#[sealed]
164
pub trait FieldsExtractExt {
165
    /// Extract named fields from [`Fields`]. `()` is returned for unit structs.
166
    ///
167
    /// # Errors
168
    /// If the fields are unnamed, `Err(Rejection::A)` is returned. If the fields are unit,
169
    /// `Err(Rejection::B)` is returned.
170
    ///
171
    /// # Example
172
    ///
173
    /// ```
174
    /// # use macroific_core::extract_fields::*;
1✔
175
    /// # use syn::{Fields, parse_quote};
176
    /// #
177
    /// let input = Fields::Unnamed(parse_quote!((u8)));
178
    /// let err: syn::Error = input.extract_named_fields().unwrap_err().into();
1✔
179
    ///
1✔
180
    /// assert_eq!(err.to_string(), "Only named fields supported");
1✔
181
    ///
1✔
182
    /// let input = Fields::Named(parse_quote!({ bar: u8 }));
183
    /// input.extract_named_fields().unwrap(); // Ok
1✔
184
    /// ```
1✔
185
    fn extract_named_fields(self) -> Result<PunctuatedFields, Rejection<FieldsUnnamed, ()>>;
1✔
186

187
    /// Extract unnamed fields from [`Fields`]. `()` is returned for unit structs.
188
    ///
189
    /// # Errors
190
    /// If the fields are named, `Err(Rejection::A)` is returned. If the fields are unit,
191
    /// `Err(Rejection::B)` is returned.
192
    ///
193
    /// # Example
194
    ///
195
    /// ```
196
    /// # use macroific_core::extract_fields::*;
1✔
197
    /// # use syn::{Fields, parse_quote};
198
    /// #
199
    /// let input = Fields::Named(parse_quote!({ bar: u8 }));
200
    /// let err: syn::Error = input.extract_unnamed_fields().unwrap_err().into();
1✔
201
    /// assert_eq!(err.to_string(), "Only unnamed fields supported");
1✔
202
    ///
1✔
203
    /// let input = Fields::Unnamed(parse_quote!((u8)));
204
    /// input.extract_unnamed_fields().unwrap(); // Ok
1✔
205
    /// ```
1✔
206
    fn extract_unnamed_fields(self) -> Result<PunctuatedFields, Rejection<FieldsNamed, ()>>;
1✔
207

208
    /// Extract named or unnamed fields
209
    ///
210
    /// # Errors
211
    /// If it's a unit struct
212
    ///
213
    /// ```
214
    /// # use macroific_core::extract_fields::*;
1✔
215
    /// # use syn::Fields;
216
    /// #
217
    /// let err: syn::Error = Fields::Unit.extract_any_fields().unwrap_err().into();
218
    /// assert_eq!(err.to_string(), "Unit structs not supported");
1✔
219
    /// ```
1✔
220
    fn extract_any_fields(self) -> syn::Result<PunctuatedFields>
1✔
221
    where
1✔
222
        Self: Sized,
1✔
223
    {
1✔
224
        match self.extract_named_fields() {
1✔
225
            Ok(fields) => Ok(fields),
×
226
            Err(Rejection::A(fields)) => Ok(fields.unnamed),
×
227
            Err(e) => Err(Error::new(
1✔
228
                e.into_syn_err().span(),
1✔
229
                "Unit structs not supported",
1✔
230
            )),
1✔
231
        }
232
    }
1✔
233
}
234

235
#[sealed]
236
impl FieldsExtractExt for Fields {
237
    /// Extract named fields from [`Fields`]. `()` is returned for unit structs.
238
    fn extract_named_fields(self) -> Result<PunctuatedFields, Rejection<FieldsUnnamed, ()>> {
3✔
239
        match self {
3✔
240
            Fields::Named(fields) => Ok(fields.named),
1✔
241
            Fields::Unnamed(fields) => Err(Rejection::A(fields)),
1✔
242
            Fields::Unit => Err(Rejection::B(())),
1✔
243
        }
244
    }
3✔
245

246
    /// Extract unnamed fields from [`Fields`]. `()` is returned for unit structs.
247
    fn extract_unnamed_fields(self) -> Result<PunctuatedFields, Rejection<FieldsNamed, ()>> {
2✔
248
        match self {
2✔
249
            Fields::Named(fields) => Err(Rejection::A(fields)),
1✔
250
            Fields::Unnamed(fields) => Ok(fields.unnamed),
1✔
251
            Fields::Unit => Err(Rejection::B(())),
×
252
        }
253
    }
2✔
254
}
255

256
/// One of two error states
257
#[allow(missing_docs)]
258
#[derive(Debug, Eq, PartialEq)]
259
pub enum Rejection<A, B> {
260
    A(A),
261
    B(B),
262
}
263

264
impl<A, B> From<Rejection<A, B>> for Error
265
where
266
    Rejection<A, B>: ToSynError,
267
{
268
    #[inline]
269
    fn from(value: Rejection<A, B>) -> Self {
5✔
270
        value.into_syn_err()
5✔
271
    }
5✔
272
}
273

274
#[sealed]
275
impl ToSynError for Rejection<FieldsUnnamed, ()> {
276
    fn to_syn_err(&self) -> Error {
2✔
277
        Error::new(
2✔
278
            match *self {
2✔
279
                Self::A(ref f) => f.span(),
1✔
280
                Self::B(()) => Span::call_site(),
1✔
281
            },
282
            "Only named fields supported",
283
        )
284
    }
2✔
285
}
286

287
#[sealed]
288
impl ToSynError for Rejection<FieldsNamed, ()> {
289
    fn to_syn_err(&self) -> Error {
1✔
290
        Error::new(
1✔
291
            match *self {
1✔
292
                Self::A(ref f) => f.span(),
1✔
NEW
293
                Self::B(()) => Span::call_site(),
×
294
            },
295
            "Only unnamed fields supported",
296
        )
297
    }
1✔
298
}
299

300
macro_rules! impl_reject {
301
    ($msg: literal => [$a: ty => $p_a: ident, $b: ty => $p_b: ident]) => {
302
        #[sealed]
303
        impl ToSynError for Rejection<$a, $b> {
304
            fn to_syn_err(&self) -> Error {
3✔
305
                Error::new(
3✔
306
                    match self {
3✔
307
                        Self::A(v) => v.$p_a.span(),
3✔
308
                        Self::B(v) => v.$p_b.span(),
×
309
                    },
310
                    $msg,
311
                )
312
            }
3✔
313
        }
314
    };
315
}
316

317
impl_reject!("Only structs supported" => [DataEnum => enum_token, DataUnion => union_token]);
318
impl_reject!("Only enums supported" => [DataStruct => struct_token, DataUnion => union_token]);
319
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

© 2026 Coveralls, Inc