deboa-bora 0.0.5

bora macro for deboa
Documentation
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TS2};
use quote::{format_ident, quote};
use syn::{
    parse_macro_input, parse_str, punctuated::Punctuated, token::Paren, Data, DeriveInput, Ident,
    LitStr, Type, TypeTuple, Visibility,
};

use crate::{
    parser::{
        api::{BoraApi, OperationEnum},
        operations::{
            delete::DeleteFieldEnum, get::GetFieldEnum, patch::PatchFieldEnum, post::PostFieldEnum,
            put::PutFieldEnum,
        },
    },
    token::utils::extract_params_from_path,
};
use titlecase::Titlecase;

#[allow(clippy::too_many_arguments)]
fn impl_function(
    deboa_method: &Ident,
    format_module: &Ident,
    api_path: &LitStr,
    method_name: &Ident,
    api_params: &TS2,
    req_body_type: &Type,
    res_body_type: &Type,
    unit_type: &Type,
) -> TS2 {
    if res_body_type.eq(unit_type) {
        quote! {
            pub async fn #method_name(&mut self, #api_params body: #req_body_type) -> Result<#res_body_type> {
                self.api.#deboa_method(format!(#api_path).as_ref()).send().await?;
                Ok(())
            }
        }
    } else {
        quote! {
            pub async fn #method_name(&mut self, #api_params body: #req_body_type) -> Result<#res_body_type> {
                self.api.#deboa_method(format!(#api_path).as_ref()).set_body_as(#format_module, body)?.send().await?.body_as(#format_module).await?
            }
        }
    }
}

pub fn bora(attr: TokenStream, item: TokenStream) -> TokenStream {
    let item = parse_macro_input!(item as DeriveInput);
    let attr = TS2::from(attr);

    let parse_attr = attr.clone().into();
    let root = parse_macro_input!(parse_attr as BoraApi);

    let name = if matches!(item.vis, Visibility::Public(_)) && matches!(item.data, Data::Struct(_))
    {
        item.ident.to_string()
    } else {
        panic!("expected public struct");
    };

    let struct_name = parse_str::<syn::Ident>(name.as_str()).unwrap();

    let mut imports = TS2::new();
    let mut struct_impl = TS2::new();
    let unit_type = Type::Tuple(TypeTuple {
        paren_token: Paren::default(),
        elems: Punctuated::new(),
    });

    root.operations.iter().fold((&mut imports, &mut struct_impl), |acc, op| {
        match op {
            OperationEnum::get(get) => {
                let fields = &get.fields;

                let method = parse_str::<syn::Ident>("get").unwrap();
                let mut method_name = Ident::new("ident", Span::call_site());
                let mut api_path = LitStr::new("lit", Span::call_site());
                let mut res_body_type = Type::Verbatim(TS2::new());
                let mut api_params = TS2::new();
                let mut format_name = Ident::new("ident", Span::call_site());
                let mut format_module = Ident::new("ident", Span::call_site());

                fields.iter().for_each(|field| match field {
                    GetFieldEnum::name(name) => {
                        method_name = Ident::new(name.value.value().as_str(), Span::call_site());
                    }
                    GetFieldEnum::path(path) => {
                        let path_info = extract_params_from_path(&path.value);
                        api_path = path_info.1;
                        api_params = path_info.0;
                    }
                    GetFieldEnum::res_body(res_body) => {
                        res_body_type = res_body.value.clone();
                    }
                    GetFieldEnum::format(format) => {
                        let format_value = format.value.value();
                        format_name = format_ident!("{}", format_value);
                        format_module = format_ident!("{}Body", format_value.titlecase());
                    }
                });

                if acc.0.is_empty() {
                    acc.0.extend(quote! {
                        use deboa_extras::http::serde::#format_name::{#format_module};
                    });
                }

                acc.1.extend(quote! {
                    pub async fn #method_name(&mut self, #api_params) -> Result<#res_body_type> {
                        self.api.#method(format!(#api_path).as_ref()).send().await?.body_as(#format_module).await
                    }
                });
            }
            OperationEnum::post(post) => {
                let fields = &post.fields;

                let method = parse_str::<syn::Ident>("post").unwrap();
                let mut method_name = Ident::new("ident", Span::call_site());
                let mut api_path = LitStr::new("lit", Span::call_site());
                let mut req_body_type = Type::Verbatim(TS2::new());
                let mut res_body_type = &unit_type;
                let mut api_params = TS2::new();
                let mut format_name = Ident::new("ident", Span::call_site());
                let mut format_module = Ident::new("ident", Span::call_site());

                fields.iter().for_each(|field| match field {
                    PostFieldEnum::name(name) => {
                        method_name = Ident::new(name.value.value().as_str(), Span::call_site());
                    }
                    PostFieldEnum::path(path) => {
                        let path_info = extract_params_from_path(&path.value);

                        api_path = path_info.1;
                        api_params = path_info.0;
                    }
                    PostFieldEnum::req_body(req_body) => {
                        req_body_type = req_body.value.clone();
                    }
                    PostFieldEnum::res_body(res_body) => {
                        res_body_type = &res_body.value;
                    }
                    PostFieldEnum::format(format) => {
                        let format_value = format.value.value();
                        let title_format_value = format_value.titlecase();
                        format_name = format_ident!("{}", format_value);
                        format_module = format_ident!("{}Body", title_format_value);
                    }
                });

                if acc.0.is_empty() {
                    acc.0.extend(quote! {
                        use deboa_extras::http::serde::#format_name::{#format_module};
                    });
                }

                if res_body_type.eq(&unit_type) {
                    acc.1.extend(impl_function(
                        &method,
                        &format_module,
                        &api_path,
                        &method_name,
                        &api_params,
                        &req_body_type,
                        res_body_type,
                        &unit_type,
                    ));
                }
            }
            OperationEnum::put(put) => {
                let fields = &put.fields;

                let method = parse_str::<syn::Ident>("put").unwrap();
                let mut method_name = Ident::new("ident", Span::call_site());
                let mut api_path = LitStr::new("lit", Span::call_site());
                let mut req_body_type = Type::Verbatim(TS2::new());
                let mut res_body_type = &unit_type;
                let mut api_params = TS2::new();
                let mut format_name = Ident::new("ident", Span::call_site());
                let mut format_module = Ident::new("ident", Span::call_site());

                fields.iter().for_each(|field| match field {
                    PutFieldEnum::name(name) => {
                        method_name = Ident::new(name.value.value().as_str(), Span::call_site());
                    }
                    PutFieldEnum::path(path) => {
                        let path_info = extract_params_from_path(&path.value);
                        api_path = path_info.1;
                        api_params = path_info.0;
                    }

                    PutFieldEnum::req_body(req_body) => {
                        req_body_type = req_body.value.clone();
                    }

                    PutFieldEnum::res_body(res_body) => {
                        res_body_type = &res_body.value;
                    }

                    PutFieldEnum::format(format) => {
                        let format_value = format.value.value();
                        let title_format_value = format_value.titlecase();
                        format_name = format_ident!("{}", format_value);
                        format_module = format_ident!("{}Body", title_format_value);
                    }
                });

                if acc.0.is_empty() {
                    acc.0.extend(quote! {
                        use deboa_extras::http::serde::#format_name::{#format_module};
                    });
                }

                acc.1.extend(impl_function(
                    &method,
                    &format_module,
                    &api_path,
                    &method_name,
                    &api_params,
                    &req_body_type,
                    res_body_type,
                    &unit_type,
                ));
            }
            OperationEnum::patch(patch) => {
                let fields = &patch.fields;

                let method = parse_str::<syn::Ident>("patch").unwrap();
                let mut method_name = Ident::new("ident", Span::call_site());
                let mut api_path = LitStr::new("lit", Span::call_site());
                let mut api_params = TS2::new();
                let mut req_body_type = Type::Verbatim(TS2::new());
                let mut res_body_type = &unit_type;
                let mut format_name = Ident::new("ident", Span::call_site());
                let mut format_module = Ident::new("ident", Span::call_site());

                fields.iter().for_each(|field| match field {
                    PatchFieldEnum::name(name) => {
                        method_name = Ident::new(name.value.value().as_str(), Span::call_site());
                    }

                    PatchFieldEnum::path(path) => {
                        let path_info = extract_params_from_path(&path.value);
                        api_path = path_info.1;
                        api_params = path_info.0;
                    }

                    PatchFieldEnum::req_body(req_body) => {
                        req_body_type = req_body.value.clone();
                    }

                    PatchFieldEnum::res_body(res_body) => {
                        res_body_type = &res_body.value;
                    }

                    PatchFieldEnum::format(format) => {
                        let format_value = format.value.value();
                        let title_format_value = format_value.titlecase();
                        format_name = format_ident!("{}", format_value);
                        format_module = format_ident!("{}Body", title_format_value);
                    }
                });

                if acc.0.is_empty() {
                    acc.0.extend(quote! {
                        use deboa_extras::http::serde::#format_name::{#format_module};
                    });
                }

                acc.1.extend(impl_function(
                    &method,
                    &format_module,
                    &api_path,
                    &method_name,
                    &api_params,
                    &req_body_type,
                    res_body_type,
                    &unit_type,
                ));
            }
            OperationEnum::delete(delete) => {
                let fields = &delete.fields;

                let method = parse_str::<syn::Ident>("delete").unwrap();
                let mut method_name = Ident::new("ident", Span::call_site());
                let mut api_path = LitStr::new("lit", Span::call_site());
                let mut api_params = TS2::new();

                fields.iter().for_each(|field| match field {
                    DeleteFieldEnum::name(name) => {
                        method_name = Ident::new(name.value.value().as_str(), Span::call_site());
                    }
                    DeleteFieldEnum::path(path) => {
                        let path_info = extract_params_from_path(&path.value);
                        api_path = path_info.1;
                        api_params = path_info.0;
                    }
                });

                acc.0.extend(quote! {});

                acc.1.extend(quote! {
                    pub async fn #method_name(&mut self, #api_params) -> Result<()> {
                        self.api.#method(#api_path).send().await?;
                        Ok(())
                    }
                });
            }
        }

        acc
    });

    let ts = quote! {
        use vamo::{Vamo as Client};
        use deboa::{response::DeboaResponse, Result};
        #imports

        pub struct #struct_name {
            api: Client
        }

        impl #struct_name {
            pub fn new(api: Client) -> Self {
                Self {
                    api
                }
            }

            #struct_impl
        }
    };

    TokenStream::from(ts)
}