diff --git a/Cargo.lock b/Cargo.lock index e9730053e2a681ea870aa2a537607b2c6ee5e318..0649f3be1f22c3bf08ee366d025fa4e0817939b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,6 +612,7 @@ dependencies = [ "clap", "conduit_api", "conduit_core", + "conduit_macros", "conduit_service", "const-str", "futures-util", @@ -712,6 +713,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "conduit_macros" +version = "0.4.5" +dependencies = [ + "conduit_core", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "conduit_router" version = "0.4.6" diff --git a/Cargo.toml b/Cargo.toml index f5c2ad241e3891294d697e53e76d7a3f3c80a4f3..5fcb03ef84687a6098a211ad7141564dd9c53faa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -413,6 +413,16 @@ default-features = false [workspace.dependencies.checked_ops] version = "0.1" +[workspace.dependencies.syn] +version = "1.0" +features = ["full", "extra-traits"] + +[workspace.dependencies.quote] +version = "1.0" + +[workspace.dependencies.proc-macro2] +version = "1.0.86" + # # Patches @@ -480,6 +490,11 @@ package = "conduit_core" path = "src/core" default-features = false +[workspace.dependencies.conduit-macros] +package = "conduit_macros" +path = "src/macros" +default-features = false + ############################################################################### # # Release profiles diff --git a/src/admin/Cargo.toml b/src/admin/Cargo.toml index 1e13fb7a97cb2041dbfb8d67913494ddd30c97a4..d756b3cbdd171a1637f638997debd0ad8bcb6f77 100644 --- a/src/admin/Cargo.toml +++ b/src/admin/Cargo.toml @@ -29,6 +29,7 @@ release_max_log_level = [ clap.workspace = true conduit-api.workspace = true conduit-core.workspace = true +conduit-macros.workspace = true conduit-service.workspace = true const-str.workspace = true futures-util.workspace = true diff --git a/src/admin/debug/commands.rs b/src/admin/debug/commands.rs index 3a7d3544911bf7175619504cdd7e04fe7bb1b198..4efefce7cc047c25155d4d2dc8a41499a41d4cae 100644 --- a/src/admin/debug/commands.rs +++ b/src/admin/debug/commands.rs @@ -477,7 +477,7 @@ pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) - #[tracing::instrument(skip(_body))] pub(super) async fn force_set_room_state_from_server( - _body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>, + _body: Vec<&str>, room_id: Box<RoomId>, server_name: Box<ServerName>, ) -> Result<RoomMessageEventContent> { if !services() .rooms @@ -691,18 +691,19 @@ pub(super) async fn resolve_true_destination( Ok(RoomMessageEventContent::text_markdown(msg)) } -#[must_use] -pub(super) fn memory_stats() -> RoomMessageEventContent { +pub(super) async fn memory_stats(_body: Vec<&str>) -> Result<RoomMessageEventContent> { let html_body = conduit::alloc::memory_stats(); if html_body.is_none() { - return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc."); + return Ok(RoomMessageEventContent::text_plain( + "malloc stats are not supported on your compiled malloc.", + )); } - RoomMessageEventContent::text_html( + Ok(RoomMessageEventContent::text_html( "This command's output can only be viewed by clients that render HTML.".to_owned(), html_body.expect("string result"), - ) + )) } #[cfg(tokio_unstable)] diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index 0df477487540241c85d122875828fcf86ae9b699..82b37c5357462920466fb6d9a5684082796bb590 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -3,11 +3,12 @@ use clap::Subcommand; use conduit::Result; +use conduit_macros::admin_command_dispatch; use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName}; -use tester::TesterCommand; -use self::commands::*; +use self::{commands::*, tester::TesterCommand}; +#[admin_command_dispatch] #[derive(Debug, Subcommand)] pub(super) enum DebugCommand { /// - Echo input of admin command @@ -176,63 +177,6 @@ pub(super) enum DebugCommand { /// - Developer test stubs #[command(subcommand)] + #[allow(non_snake_case)] Tester(TesterCommand), } - -pub(super) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { - Ok(match command { - DebugCommand::Echo { - message, - } => echo(body, message).await?, - DebugCommand::GetSigningKeys { - server_name, - cached, - } => get_signing_keys(body, server_name, cached).await?, - DebugCommand::GetAuthChain { - event_id, - } => get_auth_chain(body, event_id).await?, - DebugCommand::ParsePdu => parse_pdu(body).await?, - DebugCommand::GetPdu { - event_id, - } => get_pdu(body, event_id).await?, - DebugCommand::GetRemotePdu { - event_id, - server, - } => get_remote_pdu(body, event_id, server).await?, - DebugCommand::GetRoomState { - room_id, - } => get_room_state(body, room_id).await?, - DebugCommand::Ping { - server, - } => ping(body, server).await?, - DebugCommand::ForceDeviceListUpdates => force_device_list_updates(body).await?, - DebugCommand::ChangeLogLevel { - filter, - reset, - } => change_log_level(body, filter, reset).await?, - DebugCommand::SignJson => sign_json(body).await?, - DebugCommand::VerifyJson => verify_json(body).await?, - DebugCommand::FirstPduInRoom { - room_id, - } => first_pdu_in_room(body, room_id).await?, - DebugCommand::LatestPduInRoom { - room_id, - } => latest_pdu_in_room(body, room_id).await?, - DebugCommand::GetRemotePduList { - server, - force, - } => get_remote_pdu_list(body, server, force).await?, - DebugCommand::ForceSetRoomStateFromServer { - room_id, - server_name, - } => force_set_room_state_from_server(body, server_name, room_id).await?, - DebugCommand::ResolveTrueDestination { - server_name, - no_cache, - } => resolve_true_destination(body, server_name, no_cache).await?, - DebugCommand::MemoryStats => memory_stats(), - DebugCommand::RuntimeMetrics => runtime_metrics(body).await?, - DebugCommand::RuntimeInterval => runtime_interval(body).await?, - DebugCommand::Tester(command) => tester::process(command, body).await?, - }) -} diff --git a/src/admin/handler.rs b/src/admin/handler.rs index ff04d378294ae59b1ce731154f2020cef98afb0d..6acb19bfd2ee53822412b52b592a89332e74c68f 100644 --- a/src/admin/handler.rs +++ b/src/admin/handler.rs @@ -1,7 +1,7 @@ use std::{panic::AssertUnwindSafe, time::Instant}; use clap::{CommandFactory, Parser}; -use conduit::{error, trace, Error}; +use conduit::{error, trace, utils::string::common_prefix, Error, Result}; use futures_util::future::FutureExt; use ruma::{ events::{ @@ -10,8 +10,6 @@ }, OwnedEventId, }; - -use conduit::{utils::string::common_prefix, Result}; use service::{ admin::{Command, CommandOutput, CommandResult, HandlerResult}, Services, @@ -36,7 +34,7 @@ pub(super) fn handle(command: Command) -> HandlerResult { Box::pin(handle_comman #[tracing::instrument(skip_all, name = "admin")] async fn handle_command(command: Command) -> CommandResult { - AssertUnwindSafe(process_command(&command)) + AssertUnwindSafe(Box::pin(process_command(&command))) .catch_unwind() .await .map_err(Error::from_panic) diff --git a/src/admin/mod.rs b/src/admin/mod.rs index ff2aefd5c8fbb666b78d6990f442c597183d7395..7d752ff869207c68ca2a63e303739105911d95e1 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -1,5 +1,6 @@ #![recursion_limit = "168"] #![allow(clippy::wildcard_imports)] +#![allow(clippy::enum_glob_use)] pub(crate) mod admin; pub(crate) mod handler; diff --git a/src/macros/Cargo.toml b/src/macros/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b9a35aab754f9ea0c7e7abf42a2b616de45283cd --- /dev/null +++ b/src/macros/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "conduit_macros" +categories.workspace = true +description.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[lib] +name = "conduit_macros" +path = "mod.rs" +proc-macro = true + +[dependencies] +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true +conduit-core.workspace = true + +[lints] +workspace = true diff --git a/src/macros/admin.rs b/src/macros/admin.rs new file mode 100644 index 0000000000000000000000000000000000000000..e95e402ae6ec442bbafb6b9420ed3b6a041812a9 --- /dev/null +++ b/src/macros/admin.rs @@ -0,0 +1,50 @@ +use conduit_core::utils::string::camel_to_snake_string; +use proc_macro::{Span, TokenStream}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, Fields, Ident, ItemEnum, Variant}; + +pub(super) fn command_dispatch(args: TokenStream, input_: TokenStream) -> TokenStream { + let input = input_.clone(); + let item = parse_macro_input!(input as ItemEnum); + let _args = parse_macro_input!(args as AttributeArgs); + let arm = item.variants.iter().map(dispatch_arm); + let name = item.ident; + let q = quote! { + pub(super) async fn process(command: #name, body: Vec<&str>) -> Result<RoomMessageEventContent> { + use #name::*; + #[allow(non_snake_case)] + Ok(match command { + #( #arm )* + }) + } + }; + + [input_, q.into()].into_iter().collect::<TokenStream>() +} + +fn dispatch_arm(v: &Variant) -> TokenStream2 { + let name = &v.ident; + let target = camel_to_snake_string(&format!("{name}")); + let handler = Ident::new(&target, Span::call_site().into()); + match &v.fields { + Fields::Named(fields) => { + let field = fields.named.iter().filter_map(|f| f.ident.as_ref()); + let arg = field.clone(); + quote! { + #name { #( #field ),* } => Box::pin(#handler(body, #( #arg ),*)).await?, + } + }, + Fields::Unnamed(fields) => { + let field = &fields.unnamed.first().expect("one field"); + quote! { + #name ( #field ) => Box::pin(#handler::process(#field, body)).await?, + } + }, + Fields::Unit => { + quote! { + #name => Box::pin(#handler(body)).await?, + } + }, + } +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0aba7560eb0ecabb1f4074be6973fa77d2b6269a --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,8 @@ +mod admin; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream { + admin::command_dispatch(args, input) +}