use std::fmt::Display;
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Ident, Type, parse::Parse};
const CRATE_NAME: &str = "easy_ipc";
const SERVER_MESSAGE: &str = "server_message";
const CLIENT_MESSAGE: &str = "client_message";
#[proc_macro_derive(IpcModel, attributes(easy_ipc))]
pub fn ipc_model_derive(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let MessageAttributes {
server_message,
client_message,
} = match parse_message_type(&input) {
Ok(messages) => messages,
Err(err) => {
return err.into_compile_error().into();
}
};
let model_impl = quote! {
impl ::easy_ipc::prelude::IpcModel for #name {
type ServerMsg = #server_message;
type ClientMsg = #client_message;
fn model() -> ::std::result::Result<
::easy_ipc::prelude::ClientServerModel<Self::ClientMsg, Self::ServerMsg>,
::easy_ipc::error::InitError,
> {
::easy_ipc::ipc_model!()
}
}
};
TokenStream::from(model_impl)
}
fn parse_message_type(input: &DeriveInput) -> Result<MessageAttributes, syn::Error> {
for attr in &input.attrs {
let segments = &attr.path().segments;
if segments.len() == 1 && segments[0].ident == CRATE_NAME {
let attr: MessageAttributes = attr.parse_args().map_err(|mut e| {
e.combine(syn::Error::new(e.span(), DeriveError::GenericError));
e
})?;
return Ok(attr);
}
}
Err(syn::Error::new_spanned(input, DeriveError::GenericError))
}
enum DeriveError {
MissingServerMessage,
MissingClientMessage,
GenericError,
}
impl DeriveError {
fn default_useage() -> String {
format!(
"#[{CRATE_NAME}({CLIENT_MESSAGE} = YourClientMessage, {SERVER_MESSAGE} = YourServerMessage)]"
)
}
fn client_usage() -> String {
format!("{CLIENT_MESSAGE} = YourClientMessage")
}
fn server_usage() -> String {
format!("{SERVER_MESSAGE} = YourServerMessage")
}
}
impl Display for DeriveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeriveError::MissingServerMessage => {
write!(f, "missing {}", DeriveError::server_usage())
}
DeriveError::MissingClientMessage => {
write!(f, "missing {}", DeriveError::client_usage())
}
DeriveError::GenericError => {
write!(
f,
"invalid or missing attributes for `#[derive(Model)]` from {CRATE_NAME}\nusage: {}",
DeriveError::default_useage()
)
}
}
}
}
struct MessageAttributes {
server_message: syn::Type,
client_message: syn::Type,
}
impl Parse for MessageAttributes {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let first_ident: Ident = input.parse()?;
input.parse::<syn::Token![=]>()?;
let first_type: Type = input.parse()?;
input.parse::<syn::Token![,]>()?;
let second_ident: Ident = input.parse()?;
input.parse::<syn::Token![=]>()?;
let second_type: Type = input.parse()?;
let (server_message, client_message) = match (
first_ident.to_string().as_str(),
second_ident.to_string().as_str(),
) {
(CLIENT_MESSAGE, SERVER_MESSAGE) => (second_type, first_type),
(SERVER_MESSAGE, CLIENT_MESSAGE) => (first_type, second_type),
_ => {
let has_server_message =
first_ident == SERVER_MESSAGE || second_ident == SERVER_MESSAGE;
let has_client_message =
first_ident == CLIENT_MESSAGE || second_ident == CLIENT_MESSAGE;
if !has_server_message {
return Err(input.error(DeriveError::MissingServerMessage));
}
if !has_client_message {
return Err(input.error(DeriveError::MissingClientMessage));
}
unreachable!();
}
};
Ok(MessageAttributes {
server_message,
client_message,
})
}
}