2024-06-19 23:25:45 +02:00
|
|
|
/*
|
2024-06-19 23:27:10 +02:00
|
|
|
Eagle - A library for easy communication in full-stack Rust applications
|
2024-06-19 23:25:45 +02:00
|
|
|
Copyright (c) 2024 KodiCraft
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Affero General Public License as
|
|
|
|
published by the Free Software Foundation, either version 3 of the
|
|
|
|
License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
use proc_macro::TokenStream;
|
2024-06-20 11:34:04 +02:00
|
|
|
use quote::{format_ident, quote};
|
2024-06-20 15:26:17 +02:00
|
|
|
use syn::{parse2, spanned::Spanned, DeriveInput, Field, Ident};
|
2024-06-19 23:25:45 +02:00
|
|
|
|
2024-06-22 12:57:41 +02:00
|
|
|
#[cfg(all(feature = "tcp", feature = "unix"))]
|
|
|
|
compile_error!("You can only enable one of the 'tcp' or 'unix' features");
|
|
|
|
#[cfg(all(not(feature = "tcp"), not(feature = "unix")))]
|
|
|
|
compile_error!("You must enable either the 'tcp' or 'unix' feature");
|
|
|
|
#[cfg(all(feature = "unix", not(unix)))]
|
|
|
|
compile_error!("The 'unix' feature requires compiling for a unix target");
|
|
|
|
|
2024-06-19 23:25:45 +02:00
|
|
|
#[proc_macro_derive(Protocol)]
|
2024-06-20 15:26:17 +02:00
|
|
|
pub fn derive_protocol_derive(input: TokenStream) -> TokenStream {
|
|
|
|
let expanded = derive_protocol(input.into());
|
|
|
|
TokenStream::from(expanded)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
|
|
|
let input = parse2::<DeriveInput>(input).unwrap();
|
2024-06-19 23:25:45 +02:00
|
|
|
// Must be on an enum
|
|
|
|
let enum_ = match &input.data {
|
|
|
|
syn::Data::Enum(e) => e,
|
|
|
|
_ => {
|
|
|
|
return syn::Error::new(input.span(), "Protocol can only be derived on enums")
|
|
|
|
.to_compile_error()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let name = &input.ident;
|
2024-06-20 12:52:42 +02:00
|
|
|
|
2024-06-20 14:59:32 +02:00
|
|
|
let error_enum_name = format_ident!("__{}Error", name);
|
|
|
|
let answer_enum_name = format_ident!("__{}Answer", name);
|
|
|
|
let question_enum_name = format_ident!("__{}Question", name);
|
|
|
|
let query_enum_name = format_ident!("__{}Query", name);
|
2024-06-22 12:25:28 +02:00
|
|
|
let queries_struct_name = format_ident!("__{}Queries", name);
|
2024-06-21 15:54:48 +02:00
|
|
|
let client_connection_struct_name = format_ident!("__{}Connection", name);
|
|
|
|
let server_trait_name = format_ident!("{}ServerTrait", name);
|
2024-06-20 12:52:42 +02:00
|
|
|
let client_struct_name = format_ident!("{}Client", name);
|
|
|
|
|
2024-06-19 23:25:45 +02:00
|
|
|
let vis = &input.vis;
|
|
|
|
|
|
|
|
let mut server_trait = Vec::new();
|
2024-06-20 11:34:04 +02:00
|
|
|
let mut server_enum = Vec::new();
|
2024-06-19 23:25:45 +02:00
|
|
|
let mut client_impl = Vec::new();
|
2024-06-20 11:34:04 +02:00
|
|
|
let mut client_enum = Vec::new();
|
|
|
|
|
|
|
|
let mut query_enum = Vec::new();
|
2024-06-20 12:52:42 +02:00
|
|
|
let mut query_from_question_enum = Vec::new();
|
2024-06-20 14:59:32 +02:00
|
|
|
let mut query_set_answer = Vec::new();
|
|
|
|
let mut query_get_answer = Vec::new();
|
2024-06-19 23:25:45 +02:00
|
|
|
|
|
|
|
for variant in &enum_.variants {
|
|
|
|
// Every variant must have 2 fields
|
|
|
|
// The first field is the question (serverbound), the second field is the answer (clientbound)
|
|
|
|
if variant.fields.len() != 2 {
|
|
|
|
return syn::Error::new(
|
|
|
|
variant.span(),
|
|
|
|
"Every variant on a protocol must have exactly 2 fields",
|
|
|
|
)
|
2024-06-20 15:26:17 +02:00
|
|
|
.to_compile_error();
|
2024-06-19 23:25:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let var_name = ident_to_snake_case(&variant.ident);
|
|
|
|
let mut variant_fields = variant.fields.iter();
|
|
|
|
let question_field = variant_fields.next().unwrap();
|
|
|
|
let question_args = field_to_args(question_field);
|
2024-06-20 12:52:42 +02:00
|
|
|
let question_tuple_args = field_to_tuple_args(question_field);
|
2024-06-19 23:25:45 +02:00
|
|
|
let answer_type = variant_fields.next().unwrap().ty.clone();
|
|
|
|
|
2024-06-20 11:34:04 +02:00
|
|
|
// The variants that either the server or the client will use
|
|
|
|
// The "server" enum contains messages the server can send, the "client" enum contains messages the client can send
|
|
|
|
server_enum.push(quote! {
|
|
|
|
#var_name(#answer_type)
|
|
|
|
});
|
|
|
|
client_enum.push(quote! {
|
|
|
|
#var_name(#question_field)
|
|
|
|
});
|
2024-06-20 12:52:42 +02:00
|
|
|
// There is a From implementation for the client enum to the query enum
|
|
|
|
query_from_question_enum.push(quote! {
|
|
|
|
#question_enum_name::#var_name(question) => #query_enum_name::#var_name(question, None),
|
|
|
|
});
|
2024-06-20 14:59:32 +02:00
|
|
|
// There is a function that must be implemented to set the answer in the query enum
|
|
|
|
query_set_answer.push(quote! {
|
|
|
|
#query_enum_name::#var_name(question, answer_opt) => match answer {
|
|
|
|
#answer_enum_name::#var_name(answer) => {answer_opt.replace(answer);},
|
|
|
|
_ => panic!("The answer for this query is not the correct type."),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
// There is a function that must be implemented to get the answer from the query enum
|
|
|
|
query_get_answer.push(quote! {
|
|
|
|
#query_enum_name::#var_name(_, answer) => match answer {
|
|
|
|
Some(answer) => Some(#answer_enum_name::#var_name(answer.clone())),
|
|
|
|
None => None
|
|
|
|
},
|
|
|
|
});
|
2024-06-20 11:34:04 +02:00
|
|
|
// The function that the server needs to implement
|
2024-06-19 23:25:45 +02:00
|
|
|
server_trait.push(quote! {
|
|
|
|
fn #var_name(&mut self, #question_args) -> #answer_type;
|
|
|
|
});
|
2024-06-20 11:34:04 +02:00
|
|
|
// The function that the client uses to communicate
|
2024-06-19 23:25:45 +02:00
|
|
|
client_impl.push(quote! {
|
2024-06-21 11:51:33 +02:00
|
|
|
pub async fn #var_name(&self, #question_args) -> Result<#answer_type, #error_enum_name> {
|
2024-06-20 12:52:42 +02:00
|
|
|
let nonce = self.send(#question_enum_name::#var_name(#question_tuple_args)).await?;
|
2024-06-20 14:59:32 +02:00
|
|
|
let answer = self.recv_until(nonce).await?;
|
|
|
|
match answer {
|
|
|
|
#answer_enum_name::#var_name(answer) => Ok(answer),
|
|
|
|
_ => panic!("The answer for this query is not the correct type."),
|
|
|
|
}
|
2024-06-19 23:25:45 +02:00
|
|
|
}
|
2024-06-20 11:34:04 +02:00
|
|
|
});
|
|
|
|
// The query enum is the same as the source enum, but the second field is always wrapped in a Option<>
|
|
|
|
query_enum.push(quote! {
|
|
|
|
#var_name(#question_field, Option<#answer_type>)
|
|
|
|
});
|
2024-06-19 23:25:45 +02:00
|
|
|
}
|
|
|
|
|
2024-06-20 12:52:42 +02:00
|
|
|
// Create an error and result type for sending messages
|
|
|
|
let error_enum = quote! {
|
2024-06-21 11:28:46 +02:00
|
|
|
#[derive(Debug)]
|
2024-06-20 12:52:42 +02:00
|
|
|
#vis enum #error_enum_name {
|
2024-06-20 14:59:32 +02:00
|
|
|
SendError(tokio::sync::mpsc::error::SendError<(u64, #question_enum_name)>),
|
|
|
|
Closed,
|
2024-06-20 12:52:42 +02:00
|
|
|
}
|
|
|
|
};
|
2024-06-20 11:34:04 +02:00
|
|
|
// Create enums for the types of messages the server and client will use
|
2024-06-20 12:52:42 +02:00
|
|
|
|
|
|
|
let answer_enum = quote! {
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
|
|
|
#vis enum #answer_enum_name {
|
2024-06-20 11:34:04 +02:00
|
|
|
#(#server_enum), *
|
|
|
|
}
|
|
|
|
};
|
2024-06-20 12:52:42 +02:00
|
|
|
let question_enum = quote! {
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
|
|
|
#vis enum #question_enum_name {
|
2024-06-20 11:34:04 +02:00
|
|
|
#(#client_enum), *
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Create an enum to represent the queries the client has sent
|
|
|
|
let query_enum = quote! {
|
2024-06-20 12:52:42 +02:00
|
|
|
#[derive(Clone, Debug)]
|
2024-06-20 11:34:04 +02:00
|
|
|
#vis enum #query_enum_name {
|
|
|
|
#(#query_enum), *
|
|
|
|
}
|
2024-06-20 14:59:32 +02:00
|
|
|
impl #query_enum_name {
|
|
|
|
pub fn set_answer(&mut self, answer: #answer_enum_name) {
|
|
|
|
match self {
|
|
|
|
#(#query_set_answer)*
|
|
|
|
};
|
|
|
|
}
|
|
|
|
pub fn get_answer(&self) -> Option<#answer_enum_name> {
|
|
|
|
match self {
|
|
|
|
#(#query_get_answer)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-20 12:52:42 +02:00
|
|
|
impl From<#question_enum_name> for #query_enum_name {
|
|
|
|
fn from(query: #question_enum_name) -> Self {
|
|
|
|
match query {
|
|
|
|
#(#query_from_question_enum)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-20 11:34:04 +02:00
|
|
|
};
|
2024-06-19 23:25:45 +02:00
|
|
|
// Create a trait which the server will have to implement
|
|
|
|
let server_trait = quote! {
|
|
|
|
#vis trait #server_trait_name {
|
|
|
|
#(#server_trait)*
|
|
|
|
}
|
|
|
|
};
|
2024-06-22 12:25:28 +02:00
|
|
|
// Create a struct to hold queries behind an Arc<Mutex<>> to enable async access
|
|
|
|
// TODO: It might be a good idea to just make this a generic struct and write it in actual code
|
|
|
|
// rather than in this macro
|
|
|
|
let queries_struct = quote! {
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct #queries_struct_name {
|
|
|
|
queries: ::std::sync::Arc<::std::sync::Mutex<::std::collections::HashMap<u64, #query_enum_name>>>,
|
|
|
|
}
|
|
|
|
impl #queries_struct_name {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
queries: ::std::sync::Arc::new(::std::sync::Mutex::new(::std::collections::HashMap::new())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn insert(&self, nonce: u64, query: #query_enum_name) {
|
|
|
|
self.queries.lock().unwrap().insert(nonce, query);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get(&self, nonce: &u64) -> Option<#query_enum_name> {
|
|
|
|
self.queries.lock().unwrap().get(nonce).cloned()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_answer(&self, nonce: u64, answer: #answer_enum_name) {
|
|
|
|
if let Some(query) = self.queries.lock().unwrap().get_mut(&nonce) {
|
|
|
|
query.set_answer(answer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.queries.lock().unwrap().len()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2024-06-21 15:54:48 +02:00
|
|
|
// Create a struct to handle the connection from the client to the server
|
2024-06-22 12:57:41 +02:00
|
|
|
#[cfg(feature = "tcp")]
|
|
|
|
let stream_type = quote! { tokio::net::TcpStream };
|
|
|
|
#[cfg(feature = "tcp")]
|
|
|
|
let stream_addr_trait = quote! { tokio::net::ToSocketAddrs };
|
|
|
|
#[cfg(feature = "unix")]
|
|
|
|
let stream_type = quote! { tokio::net::UnixStream };
|
|
|
|
#[cfg(feature = "unix")]
|
|
|
|
let stream_addr_trait = quote! { std::convert::AsRef<std::path::Path> };
|
|
|
|
|
2024-06-21 15:54:48 +02:00
|
|
|
let cc_struct = quote! {
|
|
|
|
struct #client_connection_struct_name {
|
|
|
|
to_send: tokio::sync::mpsc::Receiver<(u64, #question_enum_name)>,
|
|
|
|
received: tokio::sync::mpsc::Sender<(u64, #answer_enum_name)>,
|
|
|
|
stream: #stream_type,
|
|
|
|
}
|
|
|
|
impl #client_connection_struct_name {
|
|
|
|
pub fn new(
|
|
|
|
to_send: tokio::sync::mpsc::Receiver<(u64, #question_enum_name)>,
|
|
|
|
received: tokio::sync::mpsc::Sender<(u64, #answer_enum_name)>,
|
|
|
|
stream: #stream_type,
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
to_send,
|
|
|
|
received,
|
|
|
|
stream,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn run(mut self) {
|
|
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
let mut buf = Vec::with_capacity(1024);
|
|
|
|
loop {
|
|
|
|
tokio::select! {
|
|
|
|
Some((nonce, query)) = self.to_send.recv() => {
|
|
|
|
let serialized = ron::ser::to_string(&query).expect("Failed to serialize query!");
|
|
|
|
let len = serialized.len() as u32;
|
|
|
|
self.stream.write_all(&len.to_le_bytes()).await.expect("Failed to write length!");
|
|
|
|
self.stream.write_all(serialized.as_bytes()).await.expect("Failed to write query!");
|
|
|
|
},
|
|
|
|
Ok(_) = self.stream.readable() => {
|
|
|
|
match self.stream.try_read(&mut buf) {
|
|
|
|
Ok(0) => break, // Stream closed
|
|
|
|
Ok(n) => {
|
|
|
|
// TODO: This doesn't cope with partial reads, we will handle that later
|
|
|
|
let len = u32::from_le_bytes(buf[..4].try_into().expect("Failed to convert bytes to u32"));
|
|
|
|
let serialized = std::str::from_utf8(&buf[4..(4 + len as usize)]).expect("Failed to convert bytes to string");
|
|
|
|
let query: #answer_enum_name = ron::de::from_str(serialized).expect("Failed to deserialize query!");
|
|
|
|
self.received.send((0, query)).await.expect("Failed to send query!");
|
|
|
|
buf.clear();
|
|
|
|
},
|
|
|
|
Err(ref e) if e.kind() == ::std::io::ErrorKind::WouldBlock => { continue; },
|
|
|
|
Err(e) => eprintln!("Error reading from stream: {:?}", e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2024-06-20 11:34:04 +02:00
|
|
|
// Create a struct which the client will use to communicate
|
2024-06-21 11:51:33 +02:00
|
|
|
let client_recv_queue_wrapper = format_ident!("__{}RecvQueueWrapper", name);
|
2024-06-19 23:25:45 +02:00
|
|
|
let client_struct = quote! {
|
2024-06-21 11:51:33 +02:00
|
|
|
#[derive(Clone)]
|
|
|
|
struct #client_recv_queue_wrapper {
|
|
|
|
recv_queue: ::std::sync::Arc<::tokio::sync::Mutex<tokio::sync::mpsc::Receiver<(u64, #answer_enum_name)>>>,
|
|
|
|
}
|
|
|
|
impl #client_recv_queue_wrapper {
|
|
|
|
fn new(recv_queue: tokio::sync::mpsc::Receiver<(u64, #answer_enum_name)>) -> Self {
|
|
|
|
Self {
|
|
|
|
recv_queue: ::std::sync::Arc::new(::tokio::sync::Mutex::new(recv_queue)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async fn recv(&self) -> Option<(u64, #answer_enum_name)> {
|
|
|
|
self.recv_queue.lock().await.recv().await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#[derive(Clone)]
|
2024-06-20 11:34:04 +02:00
|
|
|
#vis struct #client_struct_name {
|
2024-06-22 12:25:28 +02:00
|
|
|
queries: #queries_struct_name,
|
2024-06-20 14:59:32 +02:00
|
|
|
send_queue: tokio::sync::mpsc::Sender<(u64, #question_enum_name)>,
|
2024-06-21 11:51:33 +02:00
|
|
|
recv_queue: #client_recv_queue_wrapper,
|
2024-06-22 12:57:41 +02:00
|
|
|
connection_task: Option<::std::sync::Arc<tokio::task::JoinHandle<()>>>,
|
2024-06-21 15:54:48 +02:00
|
|
|
}
|
2024-06-19 23:25:45 +02:00
|
|
|
impl #client_struct_name {
|
2024-06-22 12:57:41 +02:00
|
|
|
pub fn new(send_queue: tokio::sync::mpsc::Sender<(u64, #question_enum_name)>,
|
|
|
|
recv_queue: tokio::sync::mpsc::Receiver<(u64, #answer_enum_name)>,
|
|
|
|
connection_task: Option<::std::sync::Arc<tokio::task::JoinHandle<()>>>) -> Self {
|
2024-06-20 11:34:04 +02:00
|
|
|
Self {
|
2024-06-22 12:25:28 +02:00
|
|
|
queries: #queries_struct_name::new(),
|
2024-06-21 11:51:33 +02:00
|
|
|
recv_queue: #client_recv_queue_wrapper::new(recv_queue),
|
2024-06-20 11:34:04 +02:00
|
|
|
send_queue,
|
2024-06-22 12:57:41 +02:00
|
|
|
connection_task,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub async fn connect<A: #stream_addr_trait>(addr: A) -> Result<Self, std::io::Error> {
|
|
|
|
let stream = #stream_type::connect(addr).await?;
|
|
|
|
let (send_queue, to_send) = tokio::sync::mpsc::channel(16);
|
|
|
|
let (to_recv, recv_queue) = tokio::sync::mpsc::channel(16);
|
|
|
|
let connection = #client_connection_struct_name::new(to_send, to_recv, stream);
|
|
|
|
let connection_task = tokio::spawn(connection.run());
|
|
|
|
Ok(Self::new(send_queue, recv_queue, Some(::std::sync::Arc::new(connection_task))))
|
|
|
|
}
|
|
|
|
pub fn close(self) {
|
|
|
|
if let Some(task) = self.connection_task {
|
|
|
|
task.abort();
|
2024-06-20 11:34:04 +02:00
|
|
|
}
|
|
|
|
}
|
2024-06-21 11:51:33 +02:00
|
|
|
async fn send(&self, query: #question_enum_name) -> Result<u64, #error_enum_name> {
|
2024-06-22 12:25:28 +02:00
|
|
|
let nonce = self.queries.len() as u64;
|
2024-06-20 14:59:32 +02:00
|
|
|
let res = self.send_queue.send((nonce, query.clone())).await;
|
2024-06-20 12:52:42 +02:00
|
|
|
match res {
|
|
|
|
Ok(_) => {
|
2024-06-22 12:25:28 +02:00
|
|
|
self.queries.insert(nonce, query.into());
|
2024-06-20 14:59:32 +02:00
|
|
|
Ok(nonce)
|
2024-06-20 12:52:42 +02:00
|
|
|
}
|
|
|
|
Err(e) => Err(#error_enum_name::SendError(e)),
|
|
|
|
}
|
|
|
|
}
|
2024-06-21 11:51:33 +02:00
|
|
|
async fn recv_until(&self, id: u64) -> Result<#answer_enum_name, #error_enum_name> {
|
2024-06-20 14:59:32 +02:00
|
|
|
loop {
|
|
|
|
// Check if we've received the answer for the query we're looking for
|
2024-06-22 12:25:28 +02:00
|
|
|
if let Some(query) = self.queries.get(&id) {
|
2024-06-20 14:59:32 +02:00
|
|
|
if let Some(answer) = query.get_answer() {
|
|
|
|
return Ok(answer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
match self.recv_queue.recv().await {
|
|
|
|
Some((nonce, answer)) => {
|
2024-06-22 12:25:28 +02:00
|
|
|
self.queries.set_answer(nonce, answer.clone());
|
2024-06-20 14:59:32 +02:00
|
|
|
}
|
|
|
|
None => return Err(#error_enum_name::Closed),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2024-06-19 23:25:45 +02:00
|
|
|
#(#client_impl)*
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let expanded = quote! {
|
2024-06-20 12:52:42 +02:00
|
|
|
#error_enum
|
|
|
|
#answer_enum
|
|
|
|
#question_enum
|
2024-06-20 11:34:04 +02:00
|
|
|
#query_enum
|
2024-06-22 12:25:28 +02:00
|
|
|
#queries_struct
|
2024-06-19 23:25:45 +02:00
|
|
|
#server_trait
|
2024-06-21 15:54:48 +02:00
|
|
|
#cc_struct
|
2024-06-19 23:25:45 +02:00
|
|
|
#client_struct
|
|
|
|
};
|
2024-06-20 15:26:17 +02:00
|
|
|
expanded
|
2024-06-19 23:25:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn ident_to_snake_case(ident: &Ident) -> Ident {
|
|
|
|
let ident = ident.to_string();
|
|
|
|
let mut out = String::new();
|
|
|
|
for (i, c) in ident.chars().enumerate() {
|
|
|
|
if c.is_uppercase() {
|
|
|
|
if i != 0 {
|
|
|
|
out.push('_');
|
|
|
|
}
|
|
|
|
out.push(c.to_lowercase().next().unwrap());
|
|
|
|
} else {
|
|
|
|
out.push(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ident::new(&out, ident.span())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn field_to_args(field: &Field) -> proc_macro2::TokenStream {
|
|
|
|
let type_ = &field.ty;
|
|
|
|
if let syn::Type::Tuple(tuple) = type_ {
|
|
|
|
let mut args = Vec::new();
|
|
|
|
for (i, elem) in tuple.elems.iter().enumerate() {
|
|
|
|
let arg = Ident::new(&format!("arg{}", i), elem.span());
|
|
|
|
args.push(quote! { #arg: #elem });
|
|
|
|
}
|
|
|
|
quote! { #( #args ), * }
|
|
|
|
} else {
|
|
|
|
quote! { arg: #type_ }
|
|
|
|
}
|
|
|
|
}
|
2024-06-20 12:52:42 +02:00
|
|
|
|
|
|
|
fn field_to_tuple_args(field: &Field) -> proc_macro2::TokenStream {
|
|
|
|
let type_ = &field.ty;
|
|
|
|
if let syn::Type::Tuple(tuple) = type_ {
|
|
|
|
let mut args = Vec::new();
|
|
|
|
for (i, elem) in tuple.elems.iter().enumerate() {
|
|
|
|
let arg = Ident::new(&format!("arg{}", i), elem.span());
|
|
|
|
args.push(quote! { #arg });
|
|
|
|
}
|
|
|
|
quote! { ( #( #args ), * ) }
|
|
|
|
} else {
|
|
|
|
quote! { (arg) }
|
|
|
|
}
|
|
|
|
}
|