1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Defines derive macro to generate implementions of 
//! each request type defines in jqdata-model crate.

extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2;
use quote::*;
use syn::{parse_macro_input, DeriveInput};

/// entrypoint of derive macro to implements HasMethod and BodyConsumer traits on
/// marked structs
#[proc_macro_derive(Jqdata, attributes(method, consume))]
pub fn derive_jqdata(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let result = match ast.data {
        syn::Data::Struct(ref s) => derive_jqdata_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with enums or unions yet"),
    };
    TokenStream::from(result)
}

fn derive_jqdata_for_struct(
    ast: &syn::DeriveInput,
    fields: &syn::Fields,
) -> proc_macro2::TokenStream {
    match *fields {
        syn::Fields::Named(..) => impl_jqdata_for_struct(&ast),
        syn::Fields::Unit => impl_jqdata_for_struct(&ast),
        syn::Fields::Unnamed(..) => panic!("doesn't work with unnamed fields yet"),
    }
}

fn impl_jqdata_for_struct(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
    let struct_name = &ast.ident;

    let request_method = ast
        .attrs
        .iter()
        .find_map(|attr| {
            if let Ok(syn::Meta::List(metalist)) = attr.parse_meta() {
                if let Some(ident) = metalist.path.get_ident() {
                    if ident == "method" {
                        if metalist.nested.len() != 1 {
                            panic!("must have one method name in request attribute");
                        }
                        return metalist.nested.first().map(nested_meta_to_string);
                    }
                }
            }
            None
        })
        .expect("must have request attribute with method name");
    
    let consume_meta = ast
        .attrs
        .iter()
        .find_map(|attr| {
            if let Ok(syn::Meta::List(metalist)) = attr.parse_meta() {
                if let Some(ident) = metalist.path.get_ident() {
                    if ident == "consume" {
                        return Some(metalist.nested);
                    }
                }
            }
            None
        })
        .expect("must have response attribute with method name");
    let consume_format = consume_meta
        .iter()
        .find_map(|m| {
            if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = m {
                if nv.path.is_ident("format") {
                    if let syn::Lit::Str(ref strlit) = nv.lit {
                        return Some(strlit.value());
                    }
                }
            }
            None
        })
        .expect("format must be set in response attribute");

    let ty = consume_meta.iter().find_map(|m| {
        if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = m {
            if nv.path.is_ident("type") {
                if let syn::Lit::Str(ref strlit) = nv.lit {
                    return Some(strlit.value());
                }
            }
        }
        None
    });

    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    let (consume_impl, output_ty) = match consume_format.as_ref() {
        "csv" => {
            let ty = ty.expect("type must be set in response attribute when format is csv");
            let single_ty: syn::Type = syn::parse_str(&ty.to_string())
                .expect("invalid type in response attribute");
            let output_ty: syn::Type = syn::parse_str(&format!("Vec<{}>", ty))
                .expect("invalid type in response attribute");
            let consume_impl = quote! {
                impl #impl_generics crate::models::CsvListBodyConsumer for #struct_name #ty_generics #where_clause {
                    type Output = #single_ty;
                } 
            };
            (consume_impl, output_ty)
        }
        "line" => {
            if ty.is_some() {
                panic!("type should not be set in response attribute when format is line");
            }
            let output_ty: syn::Type = syn::parse_str("Vec<String>").unwrap();
            let consume_impl = quote! {
                impl #impl_generics crate::models::LineBodyConsumer for #struct_name #ty_generics #where_clause {}
            };
            (consume_impl, output_ty)
        }
        "single" => {
            let output_ty = ty.expect("type must be set in response attribute when format is single");
            let output_ty: syn::Type = syn::parse_str(&output_ty).expect("invalid type in response attribute");
            let consume_impl = quote! {
                impl #impl_generics crate::models::SingleBodyConsumer<#output_ty> for #struct_name #ty_generics #where_clause {}
            };
            (consume_impl, output_ty)
        }
        "json" => {
            let output_ty = ty.expect("type must be set in response attribute when format is json");
            let output_ty: syn::Type = syn::parse_str(&output_ty).expect("invalid type in response attribute");
            let consume_impl = quote! {
                impl #impl_generics crate::models::JsonBodyConsumer for #struct_name #ty_generics #where_clause {
                    type Output = #output_ty;
                }
            };
            (consume_impl, output_ty)
        },
        _ => panic!("format {} not supported", consume_format),
    };

    quote! {
        impl #impl_generics crate::models::HasMethod for #struct_name #ty_generics #where_clause {
            fn method(&self) -> String {
                #request_method.to_owned()
            }
        }

        impl #impl_generics crate::models::BodyConsumer<#output_ty> for #struct_name #ty_generics #where_clause {
            fn consume_body<R: std::io::Read>(body: R) -> crate::Result<#output_ty> {
                Self::consume(body)
            }
        }

        #consume_impl
    }
}

fn nested_meta_to_string(nm: &syn::NestedMeta) -> String {
    match nm {
        syn::NestedMeta::Meta(meta) => match meta {
            syn::Meta::Path(path) => path.get_ident().as_ref().unwrap().to_string(),
            _ => panic!("must be single path"),
        },
        syn::NestedMeta::Lit(lit) => match lit {
            syn::Lit::Str(litstr) => {
                litstr.value()
            }
            _ => panic!("must be string literal"),
        },
    }
}