diff --git a/Cargo.lock b/Cargo.lock index a33cd951311ed..ed449e04457a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,6 +513,11 @@ name = "difference" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "digest" version = "0.7.4" @@ -1785,6 +1790,15 @@ dependencies = [ "difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pretty_assertions" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro-hack" version = "0.4.0" @@ -1922,7 +1936,7 @@ dependencies = [ "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2884,8 +2898,10 @@ version = "0.1.0" dependencies = [ "ed25519 0.1.0", "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", @@ -3771,6 +3787,7 @@ dependencies = [ "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "" "checksum datastore 0.1.0 (git+https://github.com/tomaka/libp2p-rs?rev=6aa139a12dbea3d75d898ce0b2af7fcec129e294)" = "" "checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8" +"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3cae2388d706b52f2f2f9afe280f9d768be36544bd71d1b8120cb34ea6450b55" "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" "checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e" @@ -3897,6 +3914,7 @@ dependencies = [ "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" "checksum plain_hasher 0.2.0 (git+https://github.com/paritytech/parity-common)" = "" "checksum pretty_assertions 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28ea5118e2f41bfbc974b28d88c07621befd1fa5d6ec23549be96302a1a59dd2" +"checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6" "checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0" "checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892" "checksum proc-macro2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1fa93823f53cfd0f5ac117b189aed6cfdfb2cfc0a9d82e956dd7927595ed7d46" diff --git a/substrate/runtime-support/Cargo.toml b/substrate/runtime-support/Cargo.toml index bef5065ef0f4a..d0c21056af722 100644 --- a/substrate/runtime-support/Cargo.toml +++ b/substrate/runtime-support/Cargo.toml @@ -13,6 +13,10 @@ substrate-runtime-io = { path = "../runtime-io", default_features = false } substrate-primitives = { path = "../primitives", default_features = false } substrate-codec = { path = "../codec", default_features = false } +[dev-dependencies] +pretty_assertions = "0.5.1" +serde_json = { version = "1.0" } + [features] default = ["std"] std = [ diff --git a/substrate/runtime-support/src/dispatch.rs b/substrate/runtime-support/src/dispatch.rs index 62e34ecb09ca7..7ef9c7cf1c019 100644 --- a/substrate/runtime-support/src/dispatch.rs +++ b/substrate/runtime-support/src/dispatch.rs @@ -105,6 +105,11 @@ macro_rules! decl_module { impl for $mod_type<$trait_instance: $trait_name>; $($rest)* } + + __impl_json_metadata! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } }; ( $(#[$attr:meta])* @@ -128,6 +133,11 @@ macro_rules! decl_module { impl for $mod_type<$trait_instance: $trait_name>; $($rest)* } + + __impl_json_metadata! { + impl for $mod_type<$trait_instance: $trait_name>; + $($rest)* + } } } @@ -558,3 +568,241 @@ macro_rules! impl_outer_dispatch_common { } } + +/// Implement the `json_metadata` function. +#[macro_export] +macro_rules! __impl_json_metadata { + ( + impl for $mod_type:ident<$trait_instance:ident: $trait_name:ident>; + $($rest:tt)* + ) => { + impl<$trait_instance: $trait_name> $mod_type<$trait_instance> { + pub fn json_metadata() -> &'static str { + concat!(r#"{ "name": ""#, stringify!($mod_type), r#"", "calls": ["#, + __calls_to_json!(""; $($rest)*), " ] }") + } + } + } +} + +/// Convert the list of calls into their JSON representation, joined by ",". +#[macro_export] +macro_rules! __calls_to_json { + // WITHOUT AUX + ( + $prefix_str:tt; + $(#[$attr:meta])* + pub enum $call_type:ident { + $( + fn $fn_name:ident( + $( + $param_name:ident : $param:ty + ),* + ) -> $result:ty + = $id:expr ; + )* + } + $($rest:tt)* + ) => { + concat!($prefix_str, " ", + r#"{ "name": ""#, stringify!($call_type), + r#"", "functions": {"#, + __functions_to_json!(""; $( + fn $fn_name( $( $param_name: $param ),* ) -> $result = $id; + )*), " } }", __calls_to_json!(","; $($rest)*) + ) + }; + // WITH AUX + ( + $prefix_str:tt; + $(#[$attr:meta])* + pub enum $call_type:ident where aux: $aux_type:ty { + $( + fn $fn_name:ident(aux + $( + , $param_name:ident : $param:ty + )* + ) -> $result:ty + = $id:expr ; + )* + } + $($rest:tt)* + ) => { + concat!($prefix_str, " ", + r#"{ "name": ""#, stringify!($call_type), + r#"", "functions": {"#, + __functions_to_json!(""; $aux_type; $( + fn $fn_name(aux $(, $param_name: $param )* ) -> $result = $id; + )*), " } }", __calls_to_json!(","; $($rest)*) + ) + }; + // BASE CASE + ( + $prefix_str:tt; + ) => { + "" + } +} + +/// Convert a list of function into their JSON representation, joined by ",". +#[macro_export] +macro_rules! __functions_to_json { + // WITHOUT AUX + ( + $prefix_str:tt; + fn $fn_name:ident( + $($param_name:ident : $param:ty),* + ) -> $result:ty = $id:expr ; + $($rest:tt)* + ) => { + concat!($prefix_str, " ", + __function_to_json!( + fn $fn_name( + $($param_name : $param),* + ) -> $result = $id ; + ), __functions_to_json!(","; $($rest)*) + ) + }; + // WITH AUX + ( + $prefix_str:tt; + $aux_type:ty; + fn $fn_name:ident(aux + $( + , $param_name:ident : $param:ty + )* + ) -> $result:ty = $id:expr ; + $($rest:tt)* + ) => { + concat!($prefix_str, " ", + __function_to_json!( + fn $fn_name( + aux: $aux_type + $(, $param_name : $param)* + ) -> $result = $id ; + ), __functions_to_json!(","; $aux_type; $($rest)*) + ) + }; + // BASE CASE + ( + $prefix_str:tt; + $($aux_type:ty;)* + ) => { + "" + } +} + +/// Convert a function into its JSON representation. +#[macro_export] +macro_rules! __function_to_json { + ( + fn $fn_name:ident( + $first_param_name:ident : $first_param:ty $(, $param_name:ident : $param:ty)* + ) -> $result:ty = $id:expr ; + ) => { + concat!( + r#"""#, stringify!($id), r#"""#, + r#": { "name": ""#, stringify!($fn_name), + r#"", "params": [ "#, + concat!(r#"{ "name": ""#, stringify!($first_param_name), r#"", "type": ""#, stringify!($first_param), r#"" }"# ), + $( + concat!(r#", { "name": ""#, stringify!($param_name), r#"", "type": ""#, stringify!($param), r#"" }"# ), + )* + " ] }" + ) + }; +} + +#[cfg(test)] +// Do not complain about unused `dispatch` and `dispatch_aux`. +#[allow(dead_code)] +mod tests { + use super::*; + use serde; + use serde_json; + + pub trait Trait { + type PublicAux; + } + + decl_module! { + pub struct Module; + + #[derive(Serialize, Deserialize)] + pub enum Call where aux: T::PublicAux { + fn aux_0(aux) -> Result = 0; + fn aux_1(aux, data: i32) -> Result = 1; + fn aux_2(aux, data: i32, data2: String) -> Result = 2; + } + + #[derive(Serialize, Deserialize)] + pub enum PrivCall { + fn priv_0(data: String) -> Result = 0; + fn priv_1(data: String, data2: u32) -> Result = 1; + } + } + + const EXPECTED_METADATA: &str = concat!( + r#"{ "name": "Module", "calls": [ "#, + r#"{ "name": "Call", "functions": { "#, + r#""0": { "name": "aux_0", "params": [ "#, + r#"{ "name": "aux", "type": "T::PublicAux" }"#, + r#" ] }, "#, + r#""1": { "name": "aux_1", "params": [ "#, + r#"{ "name": "aux", "type": "T::PublicAux" }, "#, + r#"{ "name": "data", "type": "i32" }"#, + r#" ] }, "#, + r#""2": { "name": "aux_2", "params": [ "#, + r#"{ "name": "aux", "type": "T::PublicAux" }, "#, + r#"{ "name": "data", "type": "i32" }, "#, + r#"{ "name": "data2", "type": "String" }"#, + r#" ] }"#, + r#" } }, "#, + r#"{ "name": "PrivCall", "functions": { "#, + r#""0": { "name": "priv_0", "params": [ "#, + r#"{ "name": "data", "type": "String" }"#, + r#" ] }, "#, + r#""1": { "name": "priv_1", "params": [ "#, + r#"{ "name": "data", "type": "String" }, "#, + r#"{ "name": "data2", "type": "u32" }"#, + r#" ] }"#, + r#" } }"#, + r#" ] }"#, + ); + + impl Module { + fn aux_0(_: &T::PublicAux) -> Result { + unreachable!() + } + + fn aux_1(_: &T::PublicAux, _: i32) -> Result { + unreachable!() + } + + fn aux_2(_: &T::PublicAux, _: i32, _: String) -> Result { + unreachable!() + } + + fn priv_0(_: String) -> Result { + unreachable!() + } + + fn priv_1(_: String, _: u32) -> Result { + unreachable!() + } + } + + struct TraitImpl {} + + impl Trait for TraitImpl { + type PublicAux = u32; + } + + #[test] + fn module_json_metadata() { + let metadata = Module::::json_metadata(); + assert_eq!(EXPECTED_METADATA, metadata); + let _: serde::de::IgnoredAny = + serde_json::from_str(metadata).expect("Is valid json syntax"); + } +} \ No newline at end of file diff --git a/substrate/runtime-support/src/lib.rs b/substrate/runtime-support/src/lib.rs index d1ccdc7d397f4..ed22c6413e90d 100644 --- a/substrate/runtime-support/src/lib.rs +++ b/substrate/runtime-support/src/lib.rs @@ -25,6 +25,15 @@ extern crate substrate_runtime_std as rstd; extern crate substrate_runtime_io as runtime_io; extern crate substrate_primitives as primitives; +#[cfg(test)] +#[macro_use] +extern crate pretty_assertions; +#[cfg(test)] +#[macro_use] +extern crate serde_derive; +#[cfg(test)] +extern crate serde_json; + #[doc(hidden)] pub extern crate substrate_codec as codec; pub use self::storage::generator::Storage as GenericStorage;