From b0c2163a0ec4b6f9565dd9f331576ad6dee2fce5 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sat, 4 Jan 2025 18:02:45 +0100 Subject: [PATCH] feat: log_errors macro for update calls --- Cargo.lock | 10 +++++ Cargo.toml | 1 + src/backend/impl/Cargo.toml | 1 + .../src/controllers/proposal_controller.rs | 2 + .../proposal_review_commit_controller.rs | 4 ++ .../controllers/proposal_review_controller.rs | 5 +++ .../controllers/user_profile_controller.rs | 4 ++ src/backend/impl/src/services/log_service.rs | 18 ++++++++ src/backend/macros/Cargo.toml | 13 ++++++ src/backend/macros/src/lib.rs | 44 +++++++++++++++++++ 10 files changed, 102 insertions(+) create mode 100644 src/backend/macros/Cargo.toml create mode 100644 src/backend/macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fdae3838..5bf482cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,6 +401,7 @@ version = "0.1.0" dependencies = [ "async-std", "backend_api", + "backend_macros", "base64 0.22.0", "candid", "candid_parser", @@ -445,6 +446,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "backend_macros" +version = "0.1.0" +dependencies = [ + "backend_api", + "quote", + "syn 2.0.48", +] + [[package]] name = "backoff" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index ec17faaf..2cc7950a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "src/backend/impl", "src/backend/external_canisters", "src/backend/logs", + "src/backend/macros", ] [profile.release] diff --git a/src/backend/impl/Cargo.toml b/src/backend/impl/Cargo.toml index 6e7db6a2..82931808 100644 --- a/src/backend/impl/Cargo.toml +++ b/src/backend/impl/Cargo.toml @@ -11,6 +11,7 @@ dev = [] [dependencies] backend_api = { path = "../api" } +backend_macros = { path = "../macros" } external_canisters = { path = "../external_canisters" } ic-cdk.workspace = true diff --git a/src/backend/impl/src/controllers/proposal_controller.rs b/src/backend/impl/src/controllers/proposal_controller.rs index ac662442..9cd7babc 100644 --- a/src/backend/impl/src/controllers/proposal_controller.rs +++ b/src/backend/impl/src/controllers/proposal_controller.rs @@ -8,10 +8,12 @@ use crate::{ use backend_api::{ ApiError, ApiResult, ListProposalsRequest, ListProposalsResponse, SyncProposalsResponse, }; +use backend_macros::log_errors; use candid::Principal; use ic_cdk::*; #[update] +#[log_errors(crate::services::log_update_call_error)] async fn sync_proposals() -> ApiResult { let calling_principal = caller(); diff --git a/src/backend/impl/src/controllers/proposal_review_commit_controller.rs b/src/backend/impl/src/controllers/proposal_review_commit_controller.rs index 764c30a7..33f1df84 100644 --- a/src/backend/impl/src/controllers/proposal_review_commit_controller.rs +++ b/src/backend/impl/src/controllers/proposal_review_commit_controller.rs @@ -2,6 +2,7 @@ use backend_api::{ ApiError, ApiResult, CreateProposalReviewCommitRequest, CreateProposalReviewCommitResponse, DeleteProposalReviewCommitRequest, UpdateProposalReviewCommitRequest, }; +use backend_macros::log_errors; use candid::Principal; use ic_cdk::*; @@ -17,6 +18,7 @@ use crate::{ }; #[update] +#[log_errors(crate::services::log_update_call_error)] async fn create_proposal_review_commit( request: CreateProposalReviewCommitRequest, ) -> ApiResult { @@ -29,6 +31,7 @@ async fn create_proposal_review_commit( } #[update] +#[log_errors(crate::services::log_update_call_error)] async fn update_proposal_review_commit( request: UpdateProposalReviewCommitRequest, ) -> ApiResult<()> { @@ -40,6 +43,7 @@ async fn update_proposal_review_commit( } #[update] +#[log_errors(crate::services::log_update_call_error)] async fn delete_proposal_review_commit( request: DeleteProposalReviewCommitRequest, ) -> ApiResult<()> { diff --git a/src/backend/impl/src/controllers/proposal_review_controller.rs b/src/backend/impl/src/controllers/proposal_review_controller.rs index 2378eaeb..b4c99486 100644 --- a/src/backend/impl/src/controllers/proposal_review_controller.rs +++ b/src/backend/impl/src/controllers/proposal_review_controller.rs @@ -16,10 +16,12 @@ use backend_api::{ GetMyProposalReviewSummaryResponse, GetProposalReviewRequest, GetProposalReviewResponse, ListProposalReviewsRequest, ListProposalReviewsResponse, UpdateProposalReviewRequest, }; +use backend_macros::log_errors; use candid::Principal; use ic_cdk::*; #[update] +#[log_errors(crate::services::log_update_call_error)] async fn create_proposal_review( request: CreateProposalReviewRequest, ) -> ApiResult { @@ -32,6 +34,7 @@ async fn create_proposal_review( } #[update] +#[log_errors(crate::services::log_update_call_error)] fn update_proposal_review(request: UpdateProposalReviewRequest) -> ApiResult<()> { let calling_principal = caller(); @@ -61,6 +64,7 @@ fn get_proposal_review(request: GetProposalReviewRequest) -> ApiResult ApiResult { @@ -73,6 +77,7 @@ async fn create_proposal_review_image( } #[update] +#[log_errors(crate::services::log_update_call_error)] fn delete_proposal_review_image(request: DeleteProposalReviewImageRequest) -> ApiResult<()> { let calling_principal = caller(); diff --git a/src/backend/impl/src/controllers/user_profile_controller.rs b/src/backend/impl/src/controllers/user_profile_controller.rs index eba9ae4b..ac885eb7 100644 --- a/src/backend/impl/src/controllers/user_profile_controller.rs +++ b/src/backend/impl/src/controllers/user_profile_controller.rs @@ -9,6 +9,7 @@ use backend_api::{ GetMyUserProfileResponse, ListReviewerProfilesResponse, UpdateMyUserProfileRequest, UpdateUserProfileRequest, }; +use backend_macros::log_errors; use candid::Principal; use ic_cdk::*; @@ -38,6 +39,7 @@ fn get_my_user_profile_history() -> ApiResult { } #[update] +#[log_errors(crate::services::log_update_call_error)] async fn create_my_user_profile() -> ApiResult { let calling_principal = caller(); @@ -48,6 +50,7 @@ async fn create_my_user_profile() -> ApiResult { } #[update] +#[log_errors(crate::services::log_update_call_error)] fn update_my_user_profile(request: UpdateMyUserProfileRequest) -> ApiResult<()> { let calling_principal = caller(); @@ -57,6 +60,7 @@ fn update_my_user_profile(request: UpdateMyUserProfileRequest) -> ApiResult<()> } #[update] +#[log_errors(crate::services::log_update_call_error)] async fn update_user_profile(request: UpdateUserProfileRequest) -> ApiResult<()> { let calling_principal = caller(); diff --git a/src/backend/impl/src/services/log_service.rs b/src/backend/impl/src/services/log_service.rs index 8860df9f..aff400dd 100644 --- a/src/backend/impl/src/services/log_service.rs +++ b/src/backend/impl/src/services/log_service.rs @@ -85,6 +85,24 @@ impl LogServiceImpl { } } +/// Mainly used with the `log_errors` macro. +/// +/// # Example +/// ``` +/// use ic_cdk::update; +/// use backend_macros::log_errors; +/// +/// #[update] +/// #[log_errors(crate::services::log_update_call_error)] +/// async fn create_proposal_review() -> ApiResult<()> { +/// Ok(()) +/// } +/// ``` +pub fn log_update_call_error(message: String, context: String) { + // We can ignore this error + let _ = LogServiceImpl::default().log_error(message, Some(context)); +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/backend/macros/Cargo.toml b/src/backend/macros/Cargo.toml new file mode 100644 index 00000000..d73aaf89 --- /dev/null +++ b/src/backend/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "backend_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +backend_api = { path = "../api" } + +syn = "2.0" +quote = "1.0" diff --git a/src/backend/macros/src/lib.rs b/src/backend/macros/src/lib.rs new file mode 100644 index 00000000..47611ade --- /dev/null +++ b/src/backend/macros/src/lib.rs @@ -0,0 +1,44 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn, Path, ReturnType}; + +#[proc_macro_attribute] +pub fn log_errors(attr: TokenStream, item: TokenStream) -> TokenStream { + let log_fn = parse_macro_input!(attr as Path); + + let input = parse_macro_input!(item as ItemFn); + let fn_name = &input.sig.ident; + let fn_args = &input.sig.inputs; + let fn_return_type = &input.sig.output; + let fn_block = &input.block; + let fn_async = &input.sig.asyncness; + + // Ensure the function returns a Result + let result = match fn_return_type { + ReturnType::Type(_, _) => { + let block = if fn_async.is_some() { + quote! { (async { #fn_block }).await } + } else { + quote! { (|| #fn_block)() } + }; + + quote! { + #fn_async fn #fn_name(#fn_args) #fn_return_type { + let result = #block; + if let backend_api::ApiResult::Err(ref e) = result { + let message = e.to_string(); + let context = stringify!(#fn_name); + #log_fn(message, context.to_string()); + } + result + } + } + } + _ => quote! { + compile_error!("The log_errors macro can only be applied to functions that return a Result."); + }, + }; + + result.into() +}