cbor-data-derive 0.8.18

A library for using CBOR as in-memory representation for working with dynamically shaped data
Documentation
use crate::{is_one_tuple, is_transparent};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Attribute, Data, DataEnum, Error, Fields, FieldsNamed, FieldsUnnamed};

pub fn code_read(data: Data, attrs: Vec<Attribute>) -> Result<TokenStream, Error> {
    if let Some(a) = attrs.iter().find(is_transparent) {
        if let Data::Struct(s) = &data {
            if is_one_tuple(&s.fields) {
                let t = &s.fields.iter().next().unwrap().ty;
                return Ok(quote_spanned! {t.span()=>
                    Ok(Self(::cbor_data::codec::ReadCbor::read_cbor(cbor)?))
                });
            }
        }
        return Err(Error::new(
            a.tokens.span(),
            "transparent representation only possible on 1-tuples",
        ));
    }

    match data {
        Data::Struct(s) => read_fields(&s.fields, quote!(Self)),
        Data::Enum(DataEnum {
            variants,
            brace_token,
            ..
        }) => {
            if variants.is_empty() {
                return Err(Error::new(
                    brace_token.span,
                    "empty enums are not supported by ReadCbor",
                ));
            }
            let vars = variants
                .iter()
                .map(|v| {
                    let i = &v.ident;
                    let n = i.to_string();

                    if let Some(a) = v.attrs.iter().find(is_transparent) {
                        if is_one_tuple(&v.fields) {
                            let t = &v.fields.iter().next().unwrap().ty;
                            return Ok(quote_spanned! {t.span()=>
                                if let Some(cbor) = d.get(#n) {
                                    Ok(Self::#i(::cbor_data::codec::ReadCbor::read_cbor(cbor)?))
                                }
                            });
                        }
                        return Err(Error::new(
                            a.tokens.span(),
                            "transparent representation only possible on 1-tuples",
                        ));
                    }

                    let c = read_fields(&v.fields, quote!(Self::#i))?;
                    Ok(quote! {
                        if let Some(cbor) = d.get(#n) {
                            #c
                        }
                    })
                })
                .collect::<Result<Vec<_>, Error>>()?;
            let known = variants.iter().map(|v| v.ident.to_string());
            Ok(quote! {
                let d = cbor.try_dict()?;
                let d = d
                    .iter()
                    .filter_map(|(k, v)| k.decode().to_str().map(|k| (k, v)))
                    .collect::<::std::collections::BTreeMap<_, _>>();
                #(#vars else)*
                {
                    Err(::cbor_data::codec::CodecError::NoKnownVariant {
                        known: &[#(#known,)*][..],
                        present: d.into_keys().map(|c| c.into_owned()).collect(),
                    })
                }
            })
        }
        Data::Union(u) => Err(Error::new(
            u.union_token.span,
            "WriteCbor does not support `union`",
        )),
    }
}

fn read_fields(f: &Fields, constructor: TokenStream) -> Result<TokenStream, Error> {
    match f {
        Fields::Named(FieldsNamed { named: f, .. }) => {
            let field = f.iter().map(|f| {
                let i = f.ident.as_ref().unwrap();
                let n = i.to_string();
                quote_spanned! {f.span()=>
                    #i: ::cbor_data::codec::ReadCbor::read_cbor(d.get(#n).ok_or(::cbor_data::codec::CodecError::MissingField(#n))?.as_ref())?,
                }
            });
            Ok(quote! {
                let d = cbor.try_dict()?;
                let d = d
                    .iter()
                    .filter_map(|(k, v)| k.decode().to_str().map(|k| (k, v)))
                    .collect::<::std::collections::BTreeMap<_, _>>();
                Ok(#constructor {
                    #(#field)*
                })
            })
        }
        Fields::Unnamed(FieldsUnnamed { unnamed: f, .. }) => {
            let n = f.len();
            let field = f.iter().enumerate().map(|(i, f)| {
                quote_spanned! {f.span()=>
                    ::cbor_data::codec::ReadCbor::read_cbor(a.get(#i).ok_or(::cbor_data::codec::CodecError::TupleSize { expected: #n, found: #i })?.as_ref())?,
                }
            });
            Ok(quote! {
                let a = cbor.try_array()?;
                Ok(#constructor(#(#field)*))
            })
        }
        Fields::Unit => Ok(quote! {
            if cbor.decode().is_null() {
                Ok(#constructor)
            } else {
                Err(::cbor_data::codec::CodecError::type_error("null", &cbor.tagged_item()))
            }
        }),
    }
}