use crate::{Asn1Type, FieldAttrs, TagMode, TagNumber, TypeAttrs};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, Ident, Path, Type};
pub(super) struct SequenceField {
pub(super) ident: Ident,
pub(super) attrs: FieldAttrs,
pub(super) field_type: Type,
}
impl SequenceField {
pub(super) fn new(field: &Field, type_attrs: &TypeAttrs) -> syn::Result<Self> {
let ident = field.ident.as_ref().cloned().ok_or_else(|| {
syn::Error::new_spanned(
field,
"no name on struct field i.e. tuple structs unsupported",
)
})?;
let attrs = FieldAttrs::parse(&field.attrs, type_attrs)?;
if attrs.asn1_type.is_some() && attrs.default.is_some() {
return Err(syn::Error::new_spanned(
ident,
"ASN.1 `type` and `default` options cannot be combined",
));
}
if attrs.default.is_some() && attrs.optional {
return Err(syn::Error::new_spanned(
ident,
"`optional` and `default` field qualifiers are mutually exclusive",
));
}
Ok(Self {
ident,
attrs,
field_type: field.ty.clone(),
})
}
pub(super) fn to_decode_tokens(&self) -> TokenStream {
let mut lowerer = LowerFieldDecoder::new(&self.attrs);
if self.attrs.asn1_type.is_some() {
lowerer.apply_asn1_type(self.attrs.optional);
}
if let Some(default) = &self.attrs.default {
debug_assert!(
self.attrs.asn1_type.is_none(),
"`type` and `default` are mutually exclusive"
);
if self.attrs.context_specific.is_none() {
lowerer.apply_default(default, &self.field_type);
}
}
lowerer.into_tokens(&self.ident)
}
pub(super) fn to_encode_tokens(&self) -> TokenStream {
let mut lowerer = LowerFieldEncoder::new(&self.ident);
let attrs = &self.attrs;
if let Some(ty) = &attrs.asn1_type {
debug_assert!(
attrs.default.is_none(),
"`type` and `default` are mutually exclusive"
);
lowerer.apply_asn1_type(ty, attrs.optional);
}
if let Some(tag_number) = &attrs.context_specific {
lowerer.apply_context_specific(tag_number, &attrs.tag_mode, attrs.optional);
}
if let Some(default) = &attrs.default {
debug_assert!(
!attrs.optional,
"`default`, and `optional` are mutually exclusive"
);
lowerer.apply_default(&self.ident, default, &self.field_type);
}
lowerer.into_tokens()
}
}
struct LowerFieldDecoder {
decoder: TokenStream,
}
impl LowerFieldDecoder {
fn new(attrs: &FieldAttrs) -> Self {
Self {
decoder: attrs.decoder(),
}
}
fn into_tokens(self, ident: &Ident) -> TokenStream {
let decoder = self.decoder;
quote! {
let #ident = #decoder;
}
}
fn apply_asn1_type(&mut self, optional: bool) {
let decoder = &self.decoder;
self.decoder = if optional {
quote! {
#decoder.map(TryInto::try_into).transpose()?
}
} else {
quote! {
#decoder.try_into()?
}
}
}
fn apply_default(&mut self, default: &Path, field_type: &Type) {
self.decoder = quote! {
Option::<#field_type>::decode(reader)?.unwrap_or_else(#default);
};
}
}
struct LowerFieldEncoder {
encoder: TokenStream,
}
impl LowerFieldEncoder {
fn new(ident: &Ident) -> Self {
Self {
encoder: quote!(self.#ident),
}
}
fn into_tokens(self) -> TokenStream {
self.encoder
}
fn apply_asn1_type(&mut self, asn1_type: &Asn1Type, optional: bool) {
let binding = &self.encoder;
self.encoder = if optional {
let map_arg = quote!(field);
let encoder = asn1_type.encoder(&map_arg);
quote! {
#binding.as_ref().map(|#map_arg| {
der::Result::Ok(#encoder)
}).transpose()?
}
} else {
let encoder = asn1_type.encoder(binding);
quote!(#encoder)
};
}
fn apply_default(&mut self, ident: &Ident, default: &Path, field_type: &Type) {
let encoder = &self.encoder;
self.encoder = quote! {
{
let default_value: #field_type = #default();
if &self.#ident == &default_value {
None
} else {
Some(#encoder)
}
}
};
}
fn apply_context_specific(
&mut self,
tag_number: &TagNumber,
tag_mode: &TagMode,
optional: bool,
) {
let encoder = &self.encoder;
let number_tokens = tag_number.to_tokens();
let mode_tokens = tag_mode.to_tokens();
if optional {
self.encoder = quote! {
#encoder.as_ref().map(|field| {
::der::asn1::ContextSpecificRef {
tag_number: #number_tokens,
tag_mode: #mode_tokens,
value: field,
}
})
};
} else {
self.encoder = quote! {
::der::asn1::ContextSpecificRef {
tag_number: #number_tokens,
tag_mode: #mode_tokens,
value: &#encoder,
}
};
}
}
}
#[cfg(test)]
mod tests {
use super::SequenceField;
use crate::{FieldAttrs, TagMode, TagNumber};
use proc_macro2::Span;
use quote::quote;
use syn::{punctuated::Punctuated, Ident, Path, PathSegment, Type, TypePath};
pub fn type_path(ident: Ident) -> Type {
let mut segments = Punctuated::new();
segments.push_value(PathSegment {
ident,
arguments: Default::default(),
});
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments,
},
})
}
#[test]
fn simple() {
let span = Span::call_site();
let ident = Ident::new("example_field", span);
let attrs = FieldAttrs {
asn1_type: None,
context_specific: None,
default: None,
extensible: false,
optional: false,
tag_mode: TagMode::Explicit,
constructed: false,
};
let field_type = Ident::new("String", span);
let field = SequenceField {
ident,
attrs,
field_type: type_path(field_type),
};
assert_eq!(
field.to_decode_tokens().to_string(),
quote! {
let example_field = reader.decode()?;
}
.to_string()
);
assert_eq!(
field.to_encode_tokens().to_string(),
quote! {
self.example_field
}
.to_string()
);
}
#[test]
fn implicit() {
let span = Span::call_site();
let ident = Ident::new("implicit_field", span);
let attrs = FieldAttrs {
asn1_type: None,
context_specific: Some(TagNumber(0)),
default: None,
extensible: false,
optional: false,
tag_mode: TagMode::Implicit,
constructed: false,
};
let field_type = Ident::new("String", span);
let field = SequenceField {
ident,
attrs,
field_type: type_path(field_type),
};
assert_eq!(
field.to_decode_tokens().to_string(),
quote! {
let implicit_field = ::der::asn1::ContextSpecific::<>::decode_implicit(
reader,
::der::TagNumber::N0
)?
.ok_or_else(|| {
der::Tag::ContextSpecific {
number: ::der::TagNumber::N0,
constructed: false
}
.value_error()
})?
.value;
}
.to_string()
);
assert_eq!(
field.to_encode_tokens().to_string(),
quote! {
::der::asn1::ContextSpecificRef {
tag_number: ::der::TagNumber::N0,
tag_mode: ::der::TagMode::Implicit,
value: &self.implicit_field,
}
}
.to_string()
);
}
}