-
Notifications
You must be signed in to change notification settings - Fork 106
feat: add optional on_complete hook to canister endpoints
#703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
05b77bd
6f1e466
b71f18a
cb9be9b
7275f17
23b17d4
654433a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,7 @@ struct ExportAttributes { | |||||||||
| pub hidden: bool, | ||||||||||
| #[darling(rename = "crate")] | ||||||||||
| pub cratename: Option<String>, | ||||||||||
| pub on_complete: Option<String>, | ||||||||||
| } | ||||||||||
|
|
||||||||||
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||||||||||
|
|
@@ -194,7 +195,29 @@ fn dfn_macro( | |||||||||
| }; | ||||||||||
| let host_compatible_name = export_name.replace(' ', ".").replace(['-', '<', '>'], "_"); | ||||||||||
|
|
||||||||||
| // 2. guard(s) | ||||||||||
| // 2. set up the various expressions required by the on_complete callback, if provided | ||||||||||
| let (on_complete_new, on_complete_arg_len, on_complete_result_len, on_complete_ident) = | ||||||||||
| if let Some(on_complete) = attrs.on_complete { | ||||||||||
| let on_complete_ident = parse_str::<Path>(&on_complete)?; | ||||||||||
| ( | ||||||||||
| quote! { | ||||||||||
| let mut __on_complete_args = #cratename::api::OnExecutionCompleteArgs::new(#function_name); | ||||||||||
| }, | ||||||||||
| quote! { | ||||||||||
| __on_complete_args.arg_bytes_len = arg_bytes.len(); | ||||||||||
| }, | ||||||||||
| quote! { | ||||||||||
| __on_complete_args.return_bytes_len = bytes.len(); | ||||||||||
| }, | ||||||||||
| quote! { | ||||||||||
| #on_complete_ident(__on_complete_args); | ||||||||||
| }, | ||||||||||
| ) | ||||||||||
| } else { | ||||||||||
| Default::default() | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 3. guard(s) | ||||||||||
| if !attrs.guard.is_empty() && method.is_lifecycle() { | ||||||||||
| return Err(Error::new( | ||||||||||
| attr_span, | ||||||||||
|
|
@@ -219,7 +242,7 @@ fn dfn_macro( | |||||||||
| #(#guards)* | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 3. decode arguments | ||||||||||
| // 4. decode arguments | ||||||||||
| let (arg_tuple, _): (Vec<Ident>, Vec<Box<Type>>) = | ||||||||||
| get_args(method, signature)?.iter().cloned().unzip(); | ||||||||||
| if !method.can_have_args() { | ||||||||||
|
|
@@ -242,32 +265,35 @@ fn dfn_macro( | |||||||||
| let arg_one = &arg_tuple[0]; | ||||||||||
| quote! { | ||||||||||
| let arg_bytes = #cratename::api::msg_arg_data(); | ||||||||||
| #on_complete_arg_len | ||||||||||
| let #arg_one = #decode_with_ident(arg_bytes); | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| quote! { | ||||||||||
| let arg_bytes = #cratename::api::msg_arg_data(); | ||||||||||
| #on_complete_arg_len | ||||||||||
| let ( #( #arg_tuple, )* ) = #decode_with_ident(arg_bytes); } | ||||||||||
| } | ||||||||||
| } else if arg_tuple.is_empty() { | ||||||||||
| quote! {} | ||||||||||
| } else { | ||||||||||
| quote! { | ||||||||||
| let arg_bytes = #cratename::api::msg_arg_data(); | ||||||||||
| #on_complete_arg_len | ||||||||||
| let mut decoder_config = ::candid::DecoderConfig::new(); | ||||||||||
| decoder_config.set_skipping_quota(10000); | ||||||||||
| let ( #( #arg_tuple, )* ) = ::candid::utils::decode_args_with_config(&arg_bytes, &decoder_config).unwrap(); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 4. function call | ||||||||||
| // 5. function call | ||||||||||
| let function_call = if signature.asyncness.is_some() { | ||||||||||
| quote! { #name ( #(#arg_tuple),* ) .await } | ||||||||||
| } else { | ||||||||||
| quote! { #name ( #(#arg_tuple),* ) } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 5. return | ||||||||||
| // 6. return | ||||||||||
| let return_length = match &signature.output { | ||||||||||
| ReturnType::Default => 0, | ||||||||||
| ReturnType::Type(_, ty) => match ty.as_ref() { | ||||||||||
|
|
@@ -307,11 +333,12 @@ fn dfn_macro( | |||||||||
| }; | ||||||||||
| quote! { | ||||||||||
| let bytes: Vec<u8> = #return_bytes; | ||||||||||
| #on_complete_result_len | ||||||||||
| #cratename::api::msg_reply(bytes); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 6. candid attributes for export_candid!() | ||||||||||
| // 7. candid attributes for export_candid!() | ||||||||||
| let candid_method_attr = if attrs.hidden { | ||||||||||
| quote! {} | ||||||||||
| } else { | ||||||||||
|
|
@@ -349,31 +376,35 @@ fn dfn_macro( | |||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // 7. exported function body | ||||||||||
| // 8. exported function body | ||||||||||
| let async_context_name = if method.is_state_persistent() { | ||||||||||
| format_ident!("in_executor_context") | ||||||||||
| } else { | ||||||||||
| format_ident!("in_query_executor_context") | ||||||||||
| }; | ||||||||||
| let body_inner = quote! { | ||||||||||
| #on_complete_new | ||||||||||
| #arg_decode | ||||||||||
| let result = #function_call; | ||||||||||
| #return_encode | ||||||||||
| #on_complete_ident | ||||||||||
|
||||||||||
| #on_complete_ident | |
| let _ = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { | |
| #on_complete_ident | |
| })); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -598,3 +598,26 @@ pub fn trap<T: AsRef<str>>(data: T) -> ! { | |
| let buf = data.as_ref(); | ||
| ic0::trap(buf.as_bytes()); | ||
| } | ||
|
|
||
| /// The arguments passed to the `on_complete` callback exposed by canister endpoint macros | ||
| #[derive(Debug)] | ||
| pub struct OnExecutionCompleteArgs { | ||
| /// The name of the canister endpoint | ||
| pub endpoint_name: &'static str, | ||
| /// The number of bytes in the request arg | ||
| pub arg_bytes_len: usize, | ||
| /// The number of bytes returned in the response | ||
| pub return_bytes_len: usize, | ||
| } | ||
|
Comment on lines
+602
to
+611
|
||
|
|
||
| impl OnExecutionCompleteArgs { | ||
| /// Creates a new `OnExecutionCompleteArgs` instance with the given endpoint name, the byte | ||
| /// lengths will be set later as the request is processed | ||
| pub fn new(endpoint_name: &'static str) -> Self { | ||
| Self { | ||
| endpoint_name, | ||
| arg_bytes_len: 0, | ||
| return_bytes_len: 0, | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generated token stream for the multi-argument
decode_withbranch appears to include an extra}at the end of thequote!block (... = #decode_with_ident(arg_bytes); }). This will produce syntactically invalid generated code for endpoints using a custom decoder with 2+ args. Remove the stray brace and consider adding a test that coversdecode_withwith multiple arguments (ideally combined withon_completeas well).