use crate::{
Asn1Type, FieldAttrs, TagMode, TypeAttrs,
attributes::{ClassNum, ClassTokens},
};
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.class_num.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 self.attrs.should_deref {
lowerer.apply_deref();
}
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(class_num) = &attrs.class_num {
lowerer.apply_class_and_number(class_num, &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_deref(&mut self) {
let encoder = &self.encoder;
self.encoder = 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_class_and_number(&mut self, class_num: &ClassNum, tag_mode: &TagMode, optional: bool) {
let encoder = &self.encoder;
let type_params = quote!(_);
let ClassTokens { ref_type, .. } = class_num.to_tokens(type_params, *tag_mode);
let number_tokens = class_num.tag_number().to_tokens();
let mode_tokens = tag_mode.to_tokens();
if optional {
self.encoder = quote! {
#encoder.as_ref().map(|field| {
#ref_type {
tag_number: #number_tokens,
tag_mode: #mode_tokens,
value: field,
}
})
};
} else {
self.encoder = quote! {
#ref_type {
tag_number: #number_tokens,
tag_mode: #mode_tokens,
value: &#encoder,
}
};
}
}
}
#[cfg(test)]
mod tests {
use super::SequenceField;
use crate::{FieldAttrs, TagMode, TagNumber, attributes::ClassNum};
use proc_macro2::Span;
use quote::quote;
use syn::{Ident, Path, PathSegment, Type, TypePath, punctuated::Punctuated};
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,
class_num: None,
default: None,
extensible: false,
optional: false,
tag_mode: TagMode::Explicit,
constructed: false,
should_deref: 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,
class_num: Some(ClassNum::ContextSpecific(TagNumber(0))),
default: None,
extensible: false,
optional: false,
tag_mode: TagMode::Implicit,
constructed: false,
should_deref: 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(0u32)
)?
.ok_or_else(|| {
::der::Tag::ContextSpecific {
number: ::der::TagNumber(0u32),
constructed: false
}
.value_error()
})?
.value;
}
.to_string()
);
assert_eq!(
field.to_encode_tokens().to_string(),
quote! {
::der::asn1::ContextSpecificRef {
tag_number: ::der::TagNumber(0u32),
tag_mode: ::der::TagMode::Implicit,
value: &self.implicit_field,
}
}
.to_string()
);
}
}