use crate::{FieldAttrs, Tag, TypeAttrs};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Fields, Ident, Path, Type, Variant};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum TagOrPath {
Tag(Tag),
Path(Path),
}
impl PartialEq<Tag> for TagOrPath {
fn eq(&self, rhs: &Tag) -> bool {
match self {
Self::Tag(lhs) => lhs == rhs,
_ => false,
}
}
}
impl From<Tag> for TagOrPath {
fn from(tag: Tag) -> Self {
Self::Tag(tag)
}
}
impl From<Path> for TagOrPath {
fn from(path: Path) -> Self {
Self::Path(path)
}
}
impl TryFrom<&Variant> for TagOrPath {
type Error = syn::Error;
fn try_from(input: &Variant) -> syn::Result<Self> {
if let Fields::Unnamed(fields) = &input.fields {
if fields.unnamed.len() == 1 {
if let Type::Path(path) = &fields.unnamed[0].ty {
return Ok(path.path.clone().into());
}
}
}
Err(syn::Error::new_spanned(
&input.ident,
"no #[asn1(type=...)] specified for enum variant",
))
}
}
impl TagOrPath {
pub fn to_tokens(&self) -> TokenStream {
match self {
Self::Tag(tag) => tag.to_tokens(),
Self::Path(path) => quote! { <#path as ::der::FixedTag>::TAG },
}
}
}
pub(super) struct ChoiceVariant {
pub(super) ident: Ident,
pub(super) attrs: FieldAttrs,
pub(super) tag: TagOrPath,
}
impl ChoiceVariant {
pub(super) fn new(input: &Variant, type_attrs: &TypeAttrs) -> syn::Result<Self> {
let ident = input.ident.clone();
let attrs = FieldAttrs::parse(&input.attrs, type_attrs)?;
if attrs.extensible {
abort!(&ident, "`extensible` is not allowed on CHOICE");
}
match &input.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => (),
_ => abort!(&ident, "enum variant must be a 1-element tuple struct"),
}
let tag = match attrs.tag()? {
Some(x) => x.into(),
None => input.try_into()?,
};
Ok(Self { ident, attrs, tag })
}
pub(super) fn to_decode_tokens(&self) -> TokenStream {
let tag = self.tag.to_tokens();
let ident = &self.ident;
let decoder = self.attrs.decoder();
match self.attrs.asn1_type {
Some(..) => quote! { #tag => Ok(Self::#ident(#decoder.try_into()?)), },
None => quote! { #tag => Ok(Self::#ident(#decoder)), },
}
}
pub(super) fn to_encode_value_tokens(&self) -> TokenStream {
let ident = &self.ident;
let binding = quote!(variant);
let encoder = self.attrs.value_encode(&binding);
quote! {
Self::#ident(#binding) => #encoder,
}
}
pub(super) fn to_value_len_tokens(&self) -> TokenStream {
let ident = &self.ident;
match self.attrs.context_specific {
Some(tag_number) => {
let tag_number = tag_number.to_tokens();
let tag_mode = self.attrs.tag_mode.to_tokens();
quote! {
Self::#ident(variant) => ::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: #tag_mode,
value: variant,
}.value_len(),
}
}
_ => quote! { Self::#ident(variant) => variant.value_len(), },
}
}
pub(super) fn to_tagged_tokens(&self) -> TokenStream {
let ident = &self.ident;
let tag = self.tag.to_tokens();
quote! {
Self::#ident(_) => #tag,
}
}
}
#[cfg(test)]
mod tests {
use super::ChoiceVariant;
use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber};
use proc_macro2::Span;
use quote::quote;
use syn::Ident;
#[test]
fn simple() {
let ident = Ident::new("ExampleVariant", Span::call_site());
let attrs = FieldAttrs::default();
let tag = Tag::Universal(Asn1Type::Utf8String).into();
let variant = ChoiceVariant { ident, attrs, tag };
assert_eq!(
variant.to_decode_tokens().to_string(),
quote! {
::der::Tag::Utf8String => Ok(Self::ExampleVariant(
reader.decode()?
)),
}
.to_string()
);
assert_eq!(
variant.to_encode_value_tokens().to_string(),
quote! {
Self::ExampleVariant(variant) => variant.encode_value(encoder),
}
.to_string()
);
assert_eq!(
variant.to_value_len_tokens().to_string(),
quote! {
Self::ExampleVariant(variant) => variant.value_len(),
}
.to_string()
);
assert_eq!(
variant.to_tagged_tokens().to_string(),
quote! {
Self::ExampleVariant(_) => ::der::Tag::Utf8String,
}
.to_string()
)
}
#[test]
fn utf8string() {
let ident = Ident::new("ExampleVariant", Span::call_site());
let attrs = FieldAttrs {
asn1_type: Some(Asn1Type::Utf8String),
..Default::default()
};
let tag = Tag::Universal(Asn1Type::Utf8String).into();
let variant = ChoiceVariant { ident, attrs, tag };
assert_eq!(
variant.to_decode_tokens().to_string(),
quote! {
::der::Tag::Utf8String => Ok(Self::ExampleVariant(
::der::asn1::Utf8StringRef::decode(reader)?
.try_into()?
)),
}
.to_string()
);
assert_eq!(
variant.to_encode_value_tokens().to_string(),
quote! {
Self::ExampleVariant(variant) => ::der::asn1::Utf8StringRef::new(variant)?.encode_value(encoder),
}
.to_string()
);
assert_eq!(
variant.to_value_len_tokens().to_string(),
quote! {
Self::ExampleVariant(variant) => variant.value_len(),
}
.to_string()
);
assert_eq!(
variant.to_tagged_tokens().to_string(),
quote! {
Self::ExampleVariant(_) => ::der::Tag::Utf8String,
}
.to_string()
)
}
#[test]
fn explicit() {
for tag_number in [0, 1, 2, 3] {
for constructed in [false, true] {
let ident = Ident::new("ExplicitVariant", Span::call_site());
let attrs = FieldAttrs {
constructed,
context_specific: Some(TagNumber(tag_number)),
..Default::default()
};
assert_eq!(attrs.tag_mode, TagMode::Explicit);
let tag = TagOrPath::Tag(Tag::ContextSpecific {
constructed,
number: TagNumber(tag_number),
});
let variant = ChoiceVariant { ident, attrs, tag };
let tag_number = TagNumber(tag_number).to_tokens();
assert_eq!(
variant.to_decode_tokens().to_string(),
quote! {
::der::Tag::ContextSpecific {
constructed: #constructed,
number: #tag_number,
} => Ok(Self::ExplicitVariant(
match ::der::asn1::ContextSpecific::<>::decode(reader)? {
field if field.tag_number == #tag_number => Some(field),
_ => None
}
.ok_or_else(|| {
der::Tag::ContextSpecific {
number: #tag_number,
constructed: #constructed
}
.value_error()
})?
.value
)),
}
.to_string()
);
assert_eq!(
variant.to_encode_value_tokens().to_string(),
quote! {
Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: ::der::TagMode::Explicit,
value: variant,
}
.encode_value(encoder),
}
.to_string()
);
assert_eq!(
variant.to_value_len_tokens().to_string(),
quote! {
Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: ::der::TagMode::Explicit,
value: variant,
}
.value_len(),
}
.to_string()
);
assert_eq!(
variant.to_tagged_tokens().to_string(),
quote! {
Self::ExplicitVariant(_) => ::der::Tag::ContextSpecific {
constructed: #constructed,
number: #tag_number,
},
}
.to_string()
)
}
}
}
#[test]
fn implicit() {
for tag_number in [0, 1, 2, 3] {
for constructed in [false, true] {
let ident = Ident::new("ImplicitVariant", Span::call_site());
let attrs = FieldAttrs {
constructed,
context_specific: Some(TagNumber(tag_number)),
tag_mode: TagMode::Implicit,
..Default::default()
};
let tag = TagOrPath::Tag(Tag::ContextSpecific {
constructed,
number: TagNumber(tag_number),
});
let variant = ChoiceVariant { ident, attrs, tag };
let tag_number = TagNumber(tag_number).to_tokens();
assert_eq!(
variant.to_decode_tokens().to_string(),
quote! {
::der::Tag::ContextSpecific {
constructed: #constructed,
number: #tag_number,
} => Ok(Self::ImplicitVariant(
::der::asn1::ContextSpecific::<>::decode_implicit(
reader,
#tag_number
)?
.ok_or_else(|| {
der::Tag::ContextSpecific {
number: #tag_number,
constructed: #constructed
}
.value_error()
})?
.value
)),
}
.to_string()
);
assert_eq!(
variant.to_encode_value_tokens().to_string(),
quote! {
Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: ::der::TagMode::Implicit,
value: variant,
}
.encode_value(encoder),
}
.to_string()
);
assert_eq!(
variant.to_value_len_tokens().to_string(),
quote! {
Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: ::der::TagMode::Implicit,
value: variant,
}
.value_len(),
}
.to_string()
);
assert_eq!(
variant.to_tagged_tokens().to_string(),
quote! {
Self::ImplicitVariant(_) => ::der::Tag::ContextSpecific {
constructed: #constructed,
number: #tag_number,
},
}
.to_string()
)
}
}
}
}