diff --git a/Cargo.toml b/Cargo.toml index 7f5937428..8aff53781 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ tracing = { version = "0.1" } either = "1" void = "1" indexmap = { version = "2.4.0", features = ["serde"] } +macro_utils = { path = "./tools/macro-utils" } # ipfs dependency rust-ipfs = "0.14.0" diff --git a/tools/macro-utils/Cargo.toml b/tools/macro-utils/Cargo.toml new file mode 100644 index 000000000..e7bb8ba77 --- /dev/null +++ b/tools/macro-utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "macro_utils" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn = { version = "2.0.87", features = ["derive", "full"] } +quote = "1.0.37" +proc-macro2 = "1.0.89" + +[lib] +proc-macro = true \ No newline at end of file diff --git a/tools/macro-utils/src/lib.rs b/tools/macro-utils/src/lib.rs new file mode 100644 index 000000000..03bb7143b --- /dev/null +++ b/tools/macro-utils/src/lib.rs @@ -0,0 +1,51 @@ +extern crate proc_macro2; + +use proc_macro::TokenStream; + +mod struct_utils; + +/// Implements a way to fetch all public implemented method for a given struct or trait. +/// It returns a tuple where the first element is the functions name and the second if its async +/// E.g. +/// ``` +/// use macro_utils::impl_funcs; +/// +/// pub struct A; +/// +/// /// For structs +/// #[impl_funcs(name="get_all_impls")] /// Optional name parameter. If not present will be called functions +/// impl A { +/// fn some_method_a() {} +/// +/// fn some_method_b() {} +/// +/// async fn some_async_method_c() {} +/// } +/// assert_eq!([("some_method_a", false), ("some_method_b", false), ("some_async_method_c", true)], A::get_all_impls()); +/// +/// +/// /// For traits +/// #[impl_funcs(name="get_all_impls")] /// Optional name parameter. If not present will be called functions +/// trait SomeTrait { +/// fn some_method_a(); +/// +/// fn some_method_b(); +/// +/// async fn some_async_method_c(); +/// } +/// +/// pub struct B; +/// impl SomeTrait for B { +/// fn some_method_a() {} +/// +/// fn some_method_b() {} +/// +/// async fn some_async_method_c() {} +/// } +/// +/// assert_eq!([("some_method_a", false), ("some_method_b", false), ("some_async_method_c", true)], B::get_all_impls()); +/// ``` +#[proc_macro_attribute] +pub fn impl_funcs(attr: TokenStream, input: TokenStream) -> TokenStream { + struct_utils::expand(attr, input).unwrap_or_else(|e| e.into_compile_error().into()) +} diff --git a/tools/macro-utils/src/struct_utils.rs b/tools/macro-utils/src/struct_utils.rs new file mode 100644 index 000000000..23db93b12 --- /dev/null +++ b/tools/macro-utils/src/struct_utils.rs @@ -0,0 +1,97 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + parse::{Parse, Parser}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + Error, ImplItem, ItemImpl, ItemTrait, Meta, Result, TraitItem, Visibility, +}; + +pub fn expand(attr: TokenStream, input: TokenStream) -> Result { + let attributes = Punctuated::::parse_terminated.parse(attr)?; + let mut name = "functions".into(); + let expanded; + for attr in attributes { + if attr.path().is_ident("name") { + if let syn::Meta::NameValue(val) = &attr { + if let syn::Expr::Lit(v) = &val.value { + if let syn::Lit::Str(s) = &v.lit { + name = s.value(); + } + } + } + } else { + return Err(Error::new( + attr.span(), + format!( + "Unrecognized attribute {:?}", + attr.path() + .get_ident() + .map(|i| i.to_string()) + .unwrap_or_default() + ), + )); + } + } + + if let Ok(mut impls) = ItemImpl::parse.parse(input.clone()) { + // Parse impl block + let mut functions = vec![]; + for item in impls.items.iter() { + if let ImplItem::Fn(function) = item { + if !function + .attrs + .iter() + .any(|attr| attr.path().is_ident("skip")) + { + let is_async = function.sig.asyncness.is_some(); + if matches!(function.vis, Visibility::Public(_) | Visibility::Inherited) { + let id = function.sig.ident.to_string(); + functions.push(quote! { + (#id, #is_async) + }); + } + } + } + } + let func = format_ident!("{name}"); + impls.items.push(parse_quote! { + fn #func() -> &'static[(&'static str, bool)] { + &[ #(#functions),* ] + } + }); + expanded = quote! { + #impls + }; + } else { + // Parse trait block + let mut trait_impl: ItemTrait = ItemTrait::parse.parse(input)?; + let mut functions = vec![]; + for item in &trait_impl.items { + if let TraitItem::Fn(function) = item { + if !function + .attrs + .iter() + .any(|attr| attr.path().is_ident("skip")) + { + let is_async = function.sig.asyncness.is_some(); + let id = function.sig.ident.to_string(); + functions.push(quote! { + (#id, #is_async) + }); + } + } + } + let func = format_ident!("{name}"); + trait_impl.items.push(parse_quote! { + fn #func() -> &'static[(&'static str, bool)] { + &[ #(#functions),* ] + } + }); + expanded = quote! { + #trait_impl + }; + } + Ok(TokenStream::from(expanded)) +} diff --git a/warp/Cargo.toml b/warp/Cargo.toml index 29d2f3b1d..665855bc4 100644 --- a/warp/Cargo.toml +++ b/warp/Cargo.toml @@ -60,6 +60,7 @@ tracing = { workspace = true } mediatype.workspace = true send_wrapper.workspace = true indexmap.workspace = true +macro_utils.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { workspace = true } diff --git a/warp/src/constellation/mod.rs b/warp/src/constellation/mod.rs index abfb7aac1..1be6e68bb 100644 --- a/warp/src/constellation/mod.rs +++ b/warp/src/constellation/mod.rs @@ -14,6 +14,7 @@ use chrono::{DateTime, Utc}; use directory::Directory; use futures::stream::BoxStream; use futures::Stream; +use macro_utils::impl_funcs; #[derive(Debug, Clone)] pub enum ConstellationEventKind { @@ -82,6 +83,7 @@ pub type ConstellationProgressStream = BoxStream<'static, Progression>; /// Interface that would provide functionality around the filesystem. #[async_trait::async_trait] +#[impl_funcs(name = "constellation_impls")] pub trait Constellation: ConstellationEvent + Extension + Sync + Send + SingleHandle { /// Provides the timestamp of when the file system was modified fn modified(&self) -> DateTime; @@ -218,6 +220,7 @@ pub trait Constellation: ConstellationEvent + Extension + Sync + Send + SingleHa } #[async_trait::async_trait] +#[impl_funcs(name = "constellation_event_impls")] pub trait ConstellationEvent: Sync + Send { /// Subscribe to an stream of events async fn constellation_subscribe(&mut self) -> Result { diff --git a/warp/src/multipass/mod.rs b/warp/src/multipass/mod.rs index 36c021571..6a02c962c 100644 --- a/warp/src/multipass/mod.rs +++ b/warp/src/multipass/mod.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Utc}; use futures::stream::BoxStream; use futures::{Stream, StreamExt}; +use macro_utils::impl_funcs; use serde::{Deserialize, Serialize}; use std::future::Future; use std::path::PathBuf; @@ -69,6 +70,7 @@ pub enum IdentityImportOption<'a> { pub type MultiPassEventStream = BoxStream<'static, MultiPassEventKind>; #[async_trait::async_trait] +#[impl_funcs(name = "multipass_impls")] pub trait MultiPass: Extension + IdentityInformation @@ -92,6 +94,7 @@ pub trait MultiPass: } #[async_trait::async_trait] +#[impl_funcs(name = "multipass_local_id_impls")] pub trait LocalIdentity: Sync + Send { /// Reference to the local [`Identity`] async fn identity(&self) -> Result; @@ -109,6 +112,7 @@ pub trait LocalIdentity: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "multipass_im_export_impls")] pub trait MultiPassImportExport: Sync + Send { /// Import identity from a specific location async fn import_identity<'a>( @@ -125,6 +129,7 @@ pub trait MultiPassImportExport: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "multipass_friends_impls")] pub trait Friends: Sync + Send { /// Send friend request to corresponding public key async fn send_request(&mut self, _: &DID) -> Result<(), Error> { @@ -203,6 +208,7 @@ pub trait Friends: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "multipass_event_impls")] pub trait MultiPassEvent: Sync + Send { /// Subscribe to an stream of events async fn multipass_subscribe(&mut self) -> Result { @@ -211,6 +217,7 @@ pub trait MultiPassEvent: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "multipass_identity_impls")] pub trait IdentityInformation: Send + Sync { /// Profile picture belonging to the `Identity` async fn identity_picture(&self, _: &DID) -> Result { diff --git a/warp/src/raygun/community.rs b/warp/src/raygun/community.rs index fa6234dc6..4f84b91c2 100644 --- a/warp/src/raygun/community.rs +++ b/warp/src/raygun/community.rs @@ -4,6 +4,7 @@ use bytes::Bytes; use chrono::{DateTime, Utc}; use futures::stream::BoxStream; use indexmap::{IndexMap, IndexSet}; +use macro_utils::impl_funcs; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -277,6 +278,7 @@ pub enum CommunityChannelPermission { SendAttachments, } +#[impl_funcs(name = "raygun_community_impls")] #[async_trait::async_trait] pub trait RayGunCommunity: Sync + Send { async fn get_community_stream( diff --git a/warp/src/raygun/group.rs b/warp/src/raygun/group.rs index 2e7b15cdd..d905af808 100644 --- a/warp/src/raygun/group.rs +++ b/warp/src/raygun/group.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use crate::crypto::DID; use crate::error::Error; +use macro_utils::impl_funcs; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; @@ -154,6 +155,7 @@ impl GroupInvitation { } // General/Base GroupChat Trait +#[impl_funcs(name = "raygun_group_chat_impls")] pub trait GroupChat: GroupInvite + GroupChatManagement { /// Join a existing group fn join_group(&mut self, _: Uuid) -> Result<(), Error> { @@ -172,6 +174,7 @@ pub trait GroupChat: GroupInvite + GroupChatManagement { } // Group Invite Management Trait +#[impl_funcs(name = "raygun_group_invite_impls")] pub trait GroupInvite { /// Sends a invite to join a group fn send_invite(&mut self, _: Uuid, _: DID) -> Result<(), Error> { @@ -195,6 +198,7 @@ pub trait GroupInvite { } // Group Admin Management Trait +#[impl_funcs(name = "raygun_group_management_impls")] pub trait GroupChatManagement { /// Create a group fn create_group(&mut self, _: &str) -> Result { diff --git a/warp/src/raygun/mod.rs b/warp/src/raygun/mod.rs index e26febe1d..9efb3abda 100644 --- a/warp/src/raygun/mod.rs +++ b/warp/src/raygun/mod.rs @@ -19,6 +19,7 @@ use bytes::Bytes; use chrono::{DateTime, Utc}; use core::ops::Range; use indexmap::{IndexMap, IndexSet}; +use macro_utils::impl_funcs; use serde::{Deserialize, Serialize}; use std::collections::{BTreeSet, HashSet}; use std::fmt::Debug; @@ -1402,6 +1403,7 @@ impl PartialEq for Location { impl Eq for Location {} #[async_trait::async_trait] +#[impl_funcs(name = "raygun_impls")] pub trait RayGun: RayGunStream + RayGunGroupConversation @@ -1589,6 +1591,7 @@ pub trait RayGunGroupConversation: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "raygun_attachment_impls")] pub trait RayGunAttachment: Sync + Send { /// Send files to a conversation. /// If no files is provided in the array, it will throw an error @@ -1627,6 +1630,7 @@ pub trait RayGunAttachment: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "raygun_stream_impls")] pub trait RayGunStream: Sync + Send { /// Subscribe to an stream of events from the conversation async fn get_conversation_stream(&mut self, _: Uuid) -> Result { @@ -1640,6 +1644,7 @@ pub trait RayGunStream: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "raygun_events_impls")] pub trait RayGunEvents: Sync + Send { /// Send an event to a conversation async fn send_event(&mut self, _: Uuid, _: MessageEvent) -> Result<(), Error> { @@ -1653,6 +1658,7 @@ pub trait RayGunEvents: Sync + Send { } #[async_trait::async_trait] +#[impl_funcs(name = "raygun_conversations_impls")] pub trait RayGunConversationInformation: Sync + Send { /// Set a description to a conversation async fn set_conversation_description(