forked from nervosnetwork/muta
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
342 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,8 @@ members = [ | |
"core/mempool", | ||
"core/network", | ||
"core/storage", | ||
"core/binding", | ||
"core/binding-macro", | ||
|
||
"protocol", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,70 @@ | ||
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
fn it_works() { | ||
assert_eq!(2 + 2, 4); | ||
} | ||
extern crate proc_macro; | ||
|
||
mod read_write; | ||
|
||
use proc_macro::TokenStream; | ||
|
||
use crate::read_write::verify_read_or_write; | ||
|
||
// `#[read]` marks a service method as readable. | ||
// | ||
// Methods marked with this macro will have: | ||
// Methods with this macro allow access (readable) from outside (RPC or other | ||
// services). | ||
// | ||
// - Verification | ||
// 1. Is it a struct method marked with #[service]? | ||
// 2. Is visibility private? | ||
// 3. Does function generics constrain `fn f<Context: RequestContext>`? | ||
// 4. Parameter signature contains `&self and ctx:Context`? | ||
// 5. Is the return value `ProtocolResult <JsonValue>`? | ||
// | ||
// example: | ||
// | ||
// struct Service; | ||
// #[service] | ||
// impl Service { | ||
// #[read] | ||
// fn test_read_fn<Context: RequestContext>( | ||
// &self, | ||
// _ctx: Context, | ||
// ) -> ProtocolResult<JsonValue> { | ||
// Ok(JsonValue::Null) | ||
// } | ||
// } | ||
#[proc_macro_attribute] | ||
pub fn read(_: TokenStream, item: TokenStream) -> TokenStream { | ||
verify_read_or_write(item, false) | ||
} | ||
|
||
// `#[write]` marks a service method as writeable. | ||
// | ||
// Methods marked with this macro will have: | ||
// - Accessibility | ||
// Methods with this macro allow access (writeable) from outside (RPC or other | ||
// services). | ||
// | ||
// - Verification | ||
// 1. Is it a struct method marked with #[service]? | ||
// 2. Is visibility private? | ||
// 3. Does function generics constrain `fn f<Context: RequestContext>`? | ||
// 4. Parameter signature contains `&mut self and ctx:Context`? | ||
// 5. Is the return value `ProtocolResult <JsonValue>`? | ||
// | ||
// example: | ||
// | ||
// struct Service; | ||
// #[service] | ||
// impl Service { | ||
// #[write] | ||
// fn test_write_fn<Context: RequestContext>( | ||
// &mut self, | ||
// _ctx: Context, | ||
// ) -> ProtocolResult<JsonValue> { | ||
// Ok(JsonValue::Null) | ||
// } | ||
// } | ||
#[proc_macro_attribute] | ||
pub fn write(_: TokenStream, item: TokenStream) -> TokenStream { | ||
verify_read_or_write(item, true) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::punctuated::Punctuated; | ||
use syn::{ | ||
parse_macro_input, FnArg, GenericArgument, GenericParam, Generics, ImplItemMethod, Path, | ||
PathArguments, ReturnType, Token, Type, TypeParamBound, Visibility, | ||
}; | ||
|
||
pub fn verify_read_or_write(item: TokenStream, mutable: bool) -> TokenStream { | ||
let method_item = parse_macro_input!(item as ImplItemMethod); | ||
|
||
let visibility = &method_item.vis; | ||
let inputs = &method_item.sig.inputs; | ||
let generics = &method_item.sig.generics; | ||
let ret_type = &method_item.sig.output; | ||
|
||
// TODO(@yejiayu): verify #[service] | ||
|
||
verify_visibiity(visibility); | ||
|
||
verify_inputs(inputs, generics, mutable); | ||
|
||
verify_ret_type(ret_type); | ||
|
||
TokenStream::from(quote! {#method_item}) | ||
} | ||
|
||
fn verify_visibiity(visibility: &Visibility) { | ||
match visibility { | ||
Visibility::Inherited => {} | ||
_ => panic!("The visibility of read/write method must be private"), | ||
}; | ||
} | ||
|
||
fn verify_inputs(inputs: &Punctuated<FnArg, Token![,]>, generics: &Generics, mutable: bool) { | ||
if inputs.len() < 2 { | ||
panic!("The two required parameters are missing: `&self/&mut self` and `RequestContext`.") | ||
} | ||
|
||
if mutable { | ||
if !arg_is_mutable_receiver(&inputs[0]) { | ||
panic!("The receiver must be `&mut self`.") | ||
} | ||
} else { | ||
if !arg_is_inmutable_receiver(&inputs[0]) { | ||
panic!("The receiver must be `&self`.") | ||
} | ||
} | ||
arg_is_request_context(&inputs[1], generics); | ||
} | ||
|
||
fn verify_ret_type(ret_type: &ReturnType) { | ||
let real_ret_type = match ret_type { | ||
ReturnType::Type(_, t) => t.as_ref(), | ||
_ => panic!("The return type of read/write method must be protocol::ProtocolResult"), | ||
}; | ||
|
||
match real_ret_type { | ||
Type::Path(type_path) => { | ||
let path = &type_path.path; | ||
let args = get_protocol_result_args(&path) | ||
.expect("The return type of read/write method must be protocol::ProtocolResult"); | ||
|
||
match args { | ||
PathArguments::AngleBracketed(arg) => { | ||
let generic_args = &arg.args; | ||
let generic_type = &generic_args[0]; | ||
generic_type_is_jsonvalue(generic_type); | ||
} | ||
_ => panic!("The return value of read/write method must be json::JsonValue"), | ||
}; | ||
} | ||
_ => panic!("The return type of read/write method must be protocol::ProtocolResult"), | ||
} | ||
} | ||
|
||
fn get_protocol_result_args(path: &Path) -> Option<&PathArguments> { | ||
// ::<a>::<b> | ||
if path.leading_colon.is_some() { | ||
return None; | ||
} | ||
|
||
// ProtocolResult<T> | ||
if path.segments.len() == 1 && path.segments[0].ident == "ProtocolResult" { | ||
return Some(&path.segments[0].arguments); | ||
} | ||
|
||
return None; | ||
} | ||
|
||
fn path_is_jsonvalue(path: &Path) -> bool { | ||
// ::<a>::<b> | ||
if path.leading_colon.is_some() { | ||
return false; | ||
} | ||
|
||
// JsonValue | ||
path.segments.len() == 1 && path.segments[0].ident == "JsonValue" | ||
} | ||
|
||
fn path_is_request_context(path: &Path, bound_name: &str) -> bool { | ||
// ::<a>::<b> | ||
if path.leading_colon.is_some() { | ||
return false; | ||
} | ||
|
||
// RequestContext | ||
path.segments.len() == 1 && path.segments[0].ident == bound_name | ||
} | ||
|
||
fn generic_type_is_jsonvalue(generic_type: &GenericArgument) -> bool { | ||
match generic_type { | ||
GenericArgument::Type(t) => match t { | ||
Type::Path(type_path) => path_is_jsonvalue(&type_path.path), | ||
_ => false, | ||
}, | ||
_ => false, | ||
} | ||
} | ||
|
||
// expect fn foo<Context: RequestContext> | ||
fn get_bounds_name_of_request_context(generics: &Generics) -> Option<String> { | ||
if generics.params.len() != 1 { | ||
return None; | ||
} | ||
|
||
let generics_type = &generics.params[0]; | ||
|
||
if let GenericParam::Type(t) = generics_type { | ||
let bound_name = t.ident.to_string(); | ||
|
||
if let TypeParamBound::Trait(bound_trait) = &t.bounds[0] { | ||
let ident = &bound_trait.path.segments[0].ident; | ||
if ident == "RequestContext" { | ||
return Some(bound_name); | ||
} | ||
} | ||
} | ||
|
||
None | ||
} | ||
|
||
// expect fn foo<Context: RequestContext>(&self, ctx: Context) | ||
fn arg_is_request_context(fn_arg: &FnArg, generics: &Generics) -> bool { | ||
let ty = match fn_arg { | ||
FnArg::Typed(pat_type) => &*pat_type.ty, | ||
_ => return false, | ||
}; | ||
|
||
let bound_name = get_bounds_name_of_request_context(generics) | ||
.expect("The `read/write` method must bound the trait `RequestContext`"); | ||
|
||
match ty { | ||
Type::Path(type_path) => path_is_request_context(&type_path.path, &bound_name), | ||
_ => false, | ||
} | ||
} | ||
|
||
// expect &mut self | ||
fn arg_is_mutable_receiver(fn_arg: &FnArg) -> bool { | ||
match fn_arg { | ||
FnArg::Receiver(receiver) => receiver.reference.is_some() && receiver.mutability.is_some(), | ||
_ => false, | ||
} | ||
} | ||
|
||
// expect &self | ||
fn arg_is_inmutable_receiver(fn_arg: &FnArg) -> bool { | ||
match fn_arg { | ||
FnArg::Receiver(receiver) => receiver.reference.is_some() && receiver.mutability.is_none(), | ||
_ => false, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#[macro_use] | ||
extern crate core_binding_macro; | ||
|
||
use bytes::Bytes; | ||
use json::JsonValue; | ||
|
||
use protocol::traits::RequestContext; | ||
use protocol::types::{Address, Hash}; | ||
use protocol::ProtocolResult; | ||
|
||
#[test] | ||
fn test_read() { | ||
struct Tests; | ||
|
||
impl Tests { | ||
#[read] | ||
fn test_read_fn<Context: RequestContext>( | ||
&self, | ||
_ctx: Context, | ||
) -> ProtocolResult<JsonValue> { | ||
Ok(JsonValue::Null) | ||
} | ||
|
||
#[write] | ||
fn test_write_fn<Context: RequestContext>( | ||
&mut self, | ||
_ctx: Context, | ||
) -> ProtocolResult<JsonValue> { | ||
Ok(JsonValue::Null) | ||
} | ||
} | ||
|
||
let context = MockRequestContext {}; | ||
|
||
let mut t = Tests {}; | ||
assert_eq!(t.test_read_fn(context.clone()).unwrap(), JsonValue::Null); | ||
assert_eq!(t.test_write_fn(context.clone()).unwrap(), JsonValue::Null); | ||
} | ||
|
||
#[derive(Clone)] | ||
struct MockRequestContext; | ||
|
||
impl RequestContext for MockRequestContext { | ||
fn sub_cycles(&self, _cycels: u64) -> ProtocolResult<()> { | ||
Ok(()) | ||
} | ||
|
||
fn get_cycles_price(&self) -> ProtocolResult<u64> { | ||
Ok(0) | ||
} | ||
|
||
fn get_cycles_limit(&self) -> ProtocolResult<u64> { | ||
Ok(0) | ||
} | ||
|
||
fn get_cycles_used(&self) -> ProtocolResult<u64> { | ||
Ok(0) | ||
} | ||
|
||
fn get_caller(&self) -> ProtocolResult<Address> { | ||
Address::from_hash(Hash::digest(Bytes::from("test"))) | ||
} | ||
|
||
fn get_current_epoch_id(&self) -> ProtocolResult<u64> { | ||
Ok(0) | ||
} | ||
|
||
fn get_service_name(&self) -> ProtocolResult<&str> { | ||
Ok("service") | ||
} | ||
|
||
fn get_service_method(&self) -> ProtocolResult<&str> { | ||
Ok("method") | ||
} | ||
|
||
fn get_payload(&self) -> ProtocolResult<&str> { | ||
Ok("payload") | ||
} | ||
} |