der_derive/choice/
variant.rs

1//! Choice variant IR and lowerings
2
3use crate::{FieldAttrs, Tag, TypeAttrs};
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::{Fields, Ident, Path, Type, Variant};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub(super) enum TagOrPath {
10    Tag(Tag),
11    Path(Path),
12}
13
14impl PartialEq<Tag> for TagOrPath {
15    fn eq(&self, rhs: &Tag) -> bool {
16        match self {
17            Self::Tag(lhs) => lhs == rhs,
18            _ => false,
19        }
20    }
21}
22
23impl From<Tag> for TagOrPath {
24    fn from(tag: Tag) -> Self {
25        Self::Tag(tag)
26    }
27}
28
29impl From<Path> for TagOrPath {
30    fn from(path: Path) -> Self {
31        Self::Path(path)
32    }
33}
34
35impl TryFrom<&Variant> for TagOrPath {
36    type Error = syn::Error;
37
38    fn try_from(input: &Variant) -> syn::Result<Self> {
39        if let Fields::Unnamed(fields) = &input.fields {
40            if fields.unnamed.len() == 1 {
41                if let Type::Path(path) = &fields.unnamed[0].ty {
42                    return Ok(path.path.clone().into());
43                }
44            }
45        }
46
47        Err(syn::Error::new_spanned(
48            &input.ident,
49            "no #[asn1(type=...)] specified for enum variant",
50        ))
51    }
52}
53
54impl TagOrPath {
55    pub fn to_tokens(&self) -> TokenStream {
56        match self {
57            Self::Tag(tag) => tag.to_tokens(),
58            Self::Path(path) => quote! { <#path as ::der::FixedTag>::TAG },
59        }
60    }
61}
62
63/// "IR" for a variant of a derived `Choice`.
64pub(super) struct ChoiceVariant {
65    /// Variant name.
66    pub(super) ident: Ident,
67
68    /// "Field" (in this case variant)-level attributes.
69    pub(super) attrs: FieldAttrs,
70
71    /// Tag for the ASN.1 type.
72    pub(super) tag: TagOrPath,
73}
74
75impl ChoiceVariant {
76    /// Create a new [`ChoiceVariant`] from the input [`Variant`].
77    pub(super) fn new(input: &Variant, type_attrs: &TypeAttrs) -> syn::Result<Self> {
78        let ident = input.ident.clone();
79        let attrs = FieldAttrs::parse(&input.attrs, type_attrs)?;
80
81        if attrs.extensible {
82            abort!(&ident, "`extensible` is not allowed on CHOICE");
83        }
84
85        // Validate that variant is a 1-element tuple struct
86        match &input.fields {
87            // TODO(tarcieri): handle 0 bindings for ASN.1 NULL
88            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => (),
89            _ => abort!(&ident, "enum variant must be a 1-element tuple struct"),
90        }
91
92        let tag = match attrs.tag()? {
93            Some(x) => x.into(),
94            None => input.try_into()?,
95        };
96
97        Ok(Self { ident, attrs, tag })
98    }
99
100    /// Derive a match arm of the impl body for `TryFrom<der::asn1::Any<'_>>`.
101    pub(super) fn to_decode_tokens(&self) -> TokenStream {
102        let tag = self.tag.to_tokens();
103        let ident = &self.ident;
104        let decoder = self.attrs.decoder();
105
106        match self.attrs.asn1_type {
107            Some(..) => quote! { #tag => Ok(Self::#ident(#decoder.try_into()?)), },
108            None => quote! { #tag => Ok(Self::#ident(#decoder)), },
109        }
110    }
111
112    /// Derive a match arm for the impl body for `der::EncodeValue::encode_value`.
113    pub(super) fn to_encode_value_tokens(&self) -> TokenStream {
114        let ident = &self.ident;
115        let binding = quote!(variant);
116        let encoder = self.attrs.value_encode(&binding);
117        quote! {
118            Self::#ident(#binding) => #encoder,
119        }
120    }
121
122    /// Derive a match arm for the impl body for `der::EncodeValue::value_len`.
123    pub(super) fn to_value_len_tokens(&self) -> TokenStream {
124        let ident = &self.ident;
125
126        match self.attrs.context_specific {
127            Some(tag_number) => {
128                let tag_number = tag_number.to_tokens();
129                let tag_mode = self.attrs.tag_mode.to_tokens();
130
131                quote! {
132                    Self::#ident(variant) => ::der::asn1::ContextSpecificRef {
133                        tag_number: #tag_number,
134                        tag_mode: #tag_mode,
135                        value: variant,
136                    }.value_len(),
137                }
138            }
139
140            _ => quote! { Self::#ident(variant) => variant.value_len(), },
141        }
142    }
143
144    /// Derive a match arm for the impl body for `der::Tagged::tag`.
145    pub(super) fn to_tagged_tokens(&self) -> TokenStream {
146        let ident = &self.ident;
147        let tag = self.tag.to_tokens();
148        quote! {
149            Self::#ident(_) => #tag,
150        }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::ChoiceVariant;
157    use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber};
158    use proc_macro2::Span;
159    use quote::quote;
160    use syn::Ident;
161
162    #[test]
163    fn simple() {
164        let ident = Ident::new("ExampleVariant", Span::call_site());
165        let attrs = FieldAttrs::default();
166        let tag = Tag::Universal(Asn1Type::Utf8String).into();
167        let variant = ChoiceVariant { ident, attrs, tag };
168
169        assert_eq!(
170            variant.to_decode_tokens().to_string(),
171            quote! {
172                ::der::Tag::Utf8String => Ok(Self::ExampleVariant(
173                    reader.decode()?
174                )),
175            }
176            .to_string()
177        );
178
179        assert_eq!(
180            variant.to_encode_value_tokens().to_string(),
181            quote! {
182                Self::ExampleVariant(variant) => variant.encode_value(encoder),
183            }
184            .to_string()
185        );
186
187        assert_eq!(
188            variant.to_value_len_tokens().to_string(),
189            quote! {
190                Self::ExampleVariant(variant) => variant.value_len(),
191            }
192            .to_string()
193        );
194
195        assert_eq!(
196            variant.to_tagged_tokens().to_string(),
197            quote! {
198                Self::ExampleVariant(_) => ::der::Tag::Utf8String,
199            }
200            .to_string()
201        )
202    }
203
204    #[test]
205    fn utf8string() {
206        let ident = Ident::new("ExampleVariant", Span::call_site());
207        let attrs = FieldAttrs {
208            asn1_type: Some(Asn1Type::Utf8String),
209            ..Default::default()
210        };
211        let tag = Tag::Universal(Asn1Type::Utf8String).into();
212        let variant = ChoiceVariant { ident, attrs, tag };
213
214        assert_eq!(
215            variant.to_decode_tokens().to_string(),
216            quote! {
217                ::der::Tag::Utf8String => Ok(Self::ExampleVariant(
218                    ::der::asn1::Utf8StringRef::decode(reader)?
219                    .try_into()?
220                )),
221            }
222            .to_string()
223        );
224
225        assert_eq!(
226            variant.to_encode_value_tokens().to_string(),
227            quote! {
228                Self::ExampleVariant(variant) => ::der::asn1::Utf8StringRef::new(variant)?.encode_value(encoder),
229            }
230            .to_string()
231        );
232
233        assert_eq!(
234            variant.to_value_len_tokens().to_string(),
235            quote! {
236                Self::ExampleVariant(variant) => variant.value_len(),
237            }
238            .to_string()
239        );
240
241        assert_eq!(
242            variant.to_tagged_tokens().to_string(),
243            quote! {
244                Self::ExampleVariant(_) => ::der::Tag::Utf8String,
245            }
246            .to_string()
247        )
248    }
249
250    #[test]
251    fn explicit() {
252        for tag_number in [0, 1, 2, 3] {
253            for constructed in [false, true] {
254                let ident = Ident::new("ExplicitVariant", Span::call_site());
255                let attrs = FieldAttrs {
256                    constructed,
257                    context_specific: Some(TagNumber(tag_number)),
258                    ..Default::default()
259                };
260                assert_eq!(attrs.tag_mode, TagMode::Explicit);
261
262                let tag = TagOrPath::Tag(Tag::ContextSpecific {
263                    constructed,
264                    number: TagNumber(tag_number),
265                });
266
267                let variant = ChoiceVariant { ident, attrs, tag };
268                let tag_number = TagNumber(tag_number).to_tokens();
269
270                assert_eq!(
271                    variant.to_decode_tokens().to_string(),
272                    quote! {
273                        ::der::Tag::ContextSpecific {
274                            constructed: #constructed,
275                            number: #tag_number,
276                        } => Ok(Self::ExplicitVariant(
277                            match ::der::asn1::ContextSpecific::<>::decode(reader)? {
278                                field if field.tag_number == #tag_number => Some(field),
279                                _ => None
280                            }
281                            .ok_or_else(|| {
282                                der::Tag::ContextSpecific {
283                                    number: #tag_number,
284                                    constructed: #constructed
285                                }
286                                .value_error()
287                            })?
288                            .value
289                        )),
290                    }
291                    .to_string()
292                );
293
294                assert_eq!(
295                    variant.to_encode_value_tokens().to_string(),
296                    quote! {
297                        Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
298                            tag_number: #tag_number,
299                            tag_mode: ::der::TagMode::Explicit,
300                            value: variant,
301                        }
302                        .encode_value(encoder),
303                    }
304                    .to_string()
305                );
306
307                assert_eq!(
308                    variant.to_value_len_tokens().to_string(),
309                    quote! {
310                        Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
311                            tag_number: #tag_number,
312                            tag_mode: ::der::TagMode::Explicit,
313                            value: variant,
314                        }
315                        .value_len(),
316                    }
317                    .to_string()
318                );
319
320                assert_eq!(
321                    variant.to_tagged_tokens().to_string(),
322                    quote! {
323                        Self::ExplicitVariant(_) => ::der::Tag::ContextSpecific {
324                            constructed: #constructed,
325                            number: #tag_number,
326                        },
327                    }
328                    .to_string()
329                )
330            }
331        }
332    }
333
334    #[test]
335    fn implicit() {
336        for tag_number in [0, 1, 2, 3] {
337            for constructed in [false, true] {
338                let ident = Ident::new("ImplicitVariant", Span::call_site());
339
340                let attrs = FieldAttrs {
341                    constructed,
342                    context_specific: Some(TagNumber(tag_number)),
343                    tag_mode: TagMode::Implicit,
344                    ..Default::default()
345                };
346
347                let tag = TagOrPath::Tag(Tag::ContextSpecific {
348                    constructed,
349                    number: TagNumber(tag_number),
350                });
351
352                let variant = ChoiceVariant { ident, attrs, tag };
353                let tag_number = TagNumber(tag_number).to_tokens();
354
355                assert_eq!(
356                    variant.to_decode_tokens().to_string(),
357                    quote! {
358                        ::der::Tag::ContextSpecific {
359                            constructed: #constructed,
360                            number: #tag_number,
361                        } => Ok(Self::ImplicitVariant(
362                            ::der::asn1::ContextSpecific::<>::decode_implicit(
363                                reader,
364                                #tag_number
365                            )?
366                            .ok_or_else(|| {
367                                der::Tag::ContextSpecific {
368                                  number: #tag_number,
369                                  constructed: #constructed
370                                }
371                                .value_error()
372                            })?
373                            .value
374                        )),
375                    }
376                    .to_string()
377                );
378
379                assert_eq!(
380                    variant.to_encode_value_tokens().to_string(),
381                    quote! {
382                        Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
383                            tag_number: #tag_number,
384                            tag_mode: ::der::TagMode::Implicit,
385                            value: variant,
386                        }
387                        .encode_value(encoder),
388                    }
389                    .to_string()
390                );
391
392                assert_eq!(
393                    variant.to_value_len_tokens().to_string(),
394                    quote! {
395                        Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
396                            tag_number: #tag_number,
397                            tag_mode: ::der::TagMode::Implicit,
398                            value: variant,
399                        }
400                        .value_len(),
401                    }
402                    .to_string()
403                );
404
405                assert_eq!(
406                    variant.to_tagged_tokens().to_string(),
407                    quote! {
408                        Self::ImplicitVariant(_) => ::der::Tag::ContextSpecific {
409                            constructed: #constructed,
410                            number: #tag_number,
411                        },
412                    }
413                    .to_string()
414                )
415            }
416        }
417    }
418}