channel-protocol 0.3.0

Enable function-based communication instead of enum message sending. All based on channels.
Documentation
use convert_case::{Case, Casing};
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;

use crate::{
    channel_protocol::{Protocol, ProtocolMessage},
    render::message::MessageSignatureKind,
};

fn message_to_fn(
    enum_message_name: &Ident,
    message @ ProtocolMessage {
        ident,
        output,
        args,
        ..
    }: &ProtocolMessage,
) -> TokenStream {
    let message_enum_ident = format_ident!("{}", ident.to_string().to_case(Case::Pascal));
    match message.signature_kind() {
        MessageSignatureKind::None => {
            quote! {
                pub fn #ident(&self) {
                    let message = #enum_message_name::#message_enum_ident;
                    self.0.send(message).unwrap();
                }
            }
        }
        MessageSignatureKind::OnlyReturn => {
            quote! {
                pub fn #ident(&self) #output {
                    let (tx, rx) = oneshot::channel();
                    let message = #enum_message_name::#message_enum_ident(tx);
                    self.0.send(message).unwrap();
                    rx.recv().unwrap()
                }
            }
        }
        MessageSignatureKind::OnlyParam => {
            let fields = args.iter().map(|arg| &arg.ident).collect_vec();
            let message_struct_name = message.struct_ident();
            quote! {
                pub fn #ident(&self, #args) {
                    let message = #enum_message_name::#message_enum_ident(#message_struct_name {
                        #(#fields,)*
                    });
                    self.0.send(message).unwrap();
                }
            }
        }
        MessageSignatureKind::ParamReturn => {
            let fields = args.iter().map(|arg| &arg.ident).collect_vec();
            let message_struct_name = message.struct_ident();
            quote! {
                pub fn #ident(&self, #args) #output {
                    let (tx, rx) = oneshot::channel();
                    let message = #enum_message_name::#message_enum_ident(#message_struct_name {
                        #(#fields,)*
                    }, tx);
                    self.0.send(message).unwrap();
                    rx.recv().unwrap()
                }
            }
        }
    }
}

fn functions(enum_message_name: &Ident, messages: &[ProtocolMessage]) -> TokenStream {
    messages
        .iter()
        .map(|m| message_to_fn(enum_message_name, m))
        .collect()
}

pub fn build(
    protocol @ Protocol {
        vis,
        ident,
        messages,
        ..
    }: &Protocol,
) -> TokenStream {
    let client_struct_name = format_ident!("{}Client", ident);
    let message_enum_ident = protocol.message_enum_ident();
    let functions = functions(&message_enum_ident, messages);

    quote! {
        #[derive(Clone)]
        #vis struct #client_struct_name(std::sync::mpsc::Sender<#message_enum_ident>);

        impl #client_struct_name {
            fn new() -> (Self, std::sync::mpsc::Receiver<#message_enum_ident>) {
                let (sender, receiver) = std::sync::mpsc::channel();
                (Self(sender), receiver)
            }

            #functions
        }
    }
}