diff --git a/.circleci/config.yml b/.circleci/config.yml index bda9682..e6ccab9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,16 +12,20 @@ jobs: keys: - backendsdk01-{{ checksum "Cargo.toml" }} - run: | #TODO: enable 'stable' and 'beta' once `allocator_api` becomes stable - rustup toolchain install nightly-2020-04-20 - rustup default nightly-2020-04-20 - rustup override set nightly-2020-04-20 - rustup target add wasm32-unknown-unknown + rustup toolchain install nightly-2021-02-27 + rustup default nightly-2021-02-27 + rustup override set nightly-2021-02-27 + rustup target add wasm32-wasi rustup component add rustfmt rustup component add clippy cargo fmt --all -- --check --color always - cargo build -v --target wasm32-unknown-unknown --all-features + + (cd fluence; cargo build -v --target wasm32-wasi --all-features) + (cd fluence; cargo clippy -v --target wasm32-wasi) + (cd fluence-test; cargo build) + cargo test -v --all-features - cargo clippy -v --target wasm32-unknown-unknown + - save_cache: paths: - ~/.cargo diff --git a/Cargo.toml b/Cargo.toml index 2ae7c56..c36734b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,38 +1,10 @@ -[package] -name = "fluence" -version = "0.5.0" # remember to update html_root_url -description = "Fluence backend SDK for developing backend applications for the Fluence network" -documentation = "https://docs.rs/fluence/" -repository = "https://github.com/fluencelabs/rust-sdk" -authors = ["Fluence Labs"] -readme = "README.md" -keywords = ["fluence", "sdk", "webassembly"] -categories = ["api-bindings", "wasm"] -license = "Apache-2.0" -edition = "2018" - -[package.metadata.docs.rs] # https://docs.rs/about -all-features = true - -[lib] -path = "src/lib.rs" -name = "fluence" - -[dependencies] -fluence-sdk-macro = { path = "crates/macro", version = "=0.5.0" } -fluence-sdk-main = { path = "crates/main", version = "=0.5.0" } -serde = "=1.0.118" - -[features] -# Print some internal logs by log_utf8_string -debug = ["fluence-sdk-main/debug"] - -# Enable logger (this will cause log_utf8_string to appear in imports) -logger = ["fluence-sdk-main/logger"] - [workspace] members = [ + "crates/fce-macro", + "crates/fce-test-macro", + "crates/fce-test-macro-impl", "crates/main", - "crates/macro", "crates/wit", + "fluence", + "fluence-test" ] diff --git a/crates/macro/Cargo.toml b/crates/fce-macro/Cargo.toml similarity index 82% rename from crates/macro/Cargo.toml rename to crates/fce-macro/Cargo.toml index 6fb8127..9c78807 100644 --- a/crates/macro/Cargo.toml +++ b/crates/fce-macro/Cargo.toml @@ -2,7 +2,7 @@ name = "fluence-sdk-macro" version = "0.5.0" # remember to update html_root_url edition = "2018" -description = "Definition of `#[invoke_handler]` attribute" +description = "Definition of the `#[fce]` macro" documentation = "https://docs.rs/fluence/fluence-sdk-macro" repository = "https://github.com/fluencelabs/rust-sdk/crates/macro" authors = ["Fluence Labs"] @@ -10,11 +10,12 @@ keywords = ["fluence", "sdk", "webassembly", "procedural_macros"] categories = ["api-bindings", "wasm"] license = "Apache-2.0" -[package.metadata.docs.rs] # https://docs.rs/about +[package.metadata.docs.rs] all-features = true [lib] proc-macro = true +doctest = false [dependencies] fluence-sdk-wit = { path = "../wit", version = "=0.5.0" } diff --git a/crates/macro/src/lib.rs b/crates/fce-macro/src/lib.rs similarity index 87% rename from crates/macro/src/lib.rs rename to crates/fce-macro/src/lib.rs index add33a9..658b1a5 100644 --- a/crates/macro/src/lib.rs +++ b/crates/fce-macro/src/lib.rs @@ -23,7 +23,7 @@ //! # Examples //! //! This example shows how a function could be exported: -//! ``` +//! ```ignore //! #[fce] //! pub fn greeting(name: String) -> String { //! format!("Hi {}", name) @@ -33,23 +33,19 @@ //! This more complex example shows how a function could be imported from another Wasm module //! and how a struct could be passed: //! -//! ``` -//! #[fce] -//! struct HostReturnValue { -//! pub error_code: i32, -//! pub outcome: Vec -//! } +//! ```ignore +//! use fluence::MountedBinaryResult; //! //! #[fce] -//! pub fn read_ipfs_file(file_path: String) -> HostReturnValue { +//! pub fn read_ipfs_file(file_path: String) -> MountedBinaryResult { //! let hash = calculate_hash(file_path); -//! ipfs(hash) +//! ipfs(vec![hash]) //! } //! //! #[fce] -//! #[link(wasm_import_module = "ipfs_node.wasm")] +//! #[link(wasm_import_module = "ipfs_node")] //! extern "C" { -//! pub fn ipfs(file_hash: String) -> HostReturnValue; +//! pub fn ipfs(file_hash: Vec) -> MountedBinaryResult; //! } //! //! ``` diff --git a/crates/fce-test-macro-impl/Cargo.toml b/crates/fce-test-macro-impl/Cargo.toml new file mode 100644 index 0000000..436cb5b --- /dev/null +++ b/crates/fce-test-macro-impl/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "fluence-sdk-test-macro-impl" +version = "0.5.0" # remember to update html_root_url +edition = "2018" +description = "Implementation of the `#[fce_test]` macro" +repository = "https://github.com/fluencelabs/rust-sdk/crates/macro-test" +authors = ["Fluence Labs"] +keywords = ["fluence", "sdk", "webassembly", "procedural_macros"] +categories = ["api-bindings", "wasm"] +license = "Apache-2.0" + +[package.metadata.docs.rs] +all-features = true + +[dependencies] +fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] } +fce-wit-parser = "0.4.0" + +darling = "0.12.2" +quote = "1.0.9" +proc-macro2 = "1.0.24" +proc-macro-error = { version = "1.0.4", default-features = false } +syn = { version = '1.0.64', features = ['full'] } +thiserror = "1.0.24" diff --git a/crates/fce-test-macro-impl/src/attributes.rs b/crates/fce-test-macro-impl/src/attributes.rs new file mode 100644 index 0000000..bd5e482 --- /dev/null +++ b/crates/fce-test-macro-impl/src/attributes.rs @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use darling::FromMeta; + +/// Describes attributes of `fce_test` macro. +#[derive(Debug, Default, Clone, FromMeta)] +pub(crate) struct FCETestAttributes { + /// Path to a config file of a tested service. + pub(crate) config_path: String, + + /// Path to compiled modules of a service. + #[darling(default)] + pub(crate) modules_dir: Option, +} diff --git a/crates/fce-test-macro-impl/src/errors.rs b/crates/fce-test-macro-impl/src/errors.rs new file mode 100644 index 0000000..2497763 --- /dev/null +++ b/crates/fce-test-macro-impl/src/errors.rs @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use fce_wit_parser::WITParserError; +use fluence_app_service::AppServiceError; + +use darling::Error as DarlingError; +use syn::Error as SynError; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum TestGeneratorError { + #[error("Can't load Wasm modules into FCE: {0}")] + WITParserError(#[from] WITParserError), + + #[error("{0}")] + CorruptedITSection(#[from] CorruptedITSection), + + #[error("{0}")] + SynError(#[from] SynError), + + #[error("Can't load Wasm modules from the provided config: {0}")] + ConfigLoadError(#[from] AppServiceError), + + #[error("{0}")] + AttributesError(#[from] DarlingError), + + #[error( + "neither modules_dir attribute specified nor service config contains modules_dir, please specify one of them" + )] + ModulesDirUnspecified, + + #[error("a Wasm file compiled with newer version of sdk that supports multi-value")] + ManyFnOutputsUnsupported, +} + +#[derive(Debug, ThisError)] +pub enum CorruptedITSection { + #[error("record with {0} is absent in embedded IT section")] + AbsentRecord(u64), +} diff --git a/crates/fce-test-macro-impl/src/fce_test/config_utils.rs b/crates/fce-test-macro-impl/src/fce_test/config_utils.rs new file mode 100644 index 0000000..77450da --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/config_utils.rs @@ -0,0 +1,88 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::TResult; + +use fluence_app_service::TomlAppServiceConfig; +use fce_wit_parser::module_raw_interface; +use fce_wit_parser::interface::FCEModuleInterface; + +use std::path::PathBuf; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) struct Module<'m> { + pub name: &'m str, + pub interface: FCEModuleInterface, +} + +impl<'m> Module<'m> { + fn new(name: &'m str, interface: FCEModuleInterface) -> Self { + Self { name, interface } + } +} + +/// Returns all modules the provided config consists of. +pub(super) fn collect_modules( + config: &TomlAppServiceConfig, + modules_dir: PathBuf, +) -> TResult>> { + let module_paths = collect_module_paths(config, modules_dir); + + module_paths + .into_iter() + .map(|(name, path)| { + module_raw_interface(path).map(|interface| Module::new(name, interface)) + }) + .collect::, _>>() + .map_err(Into::into) +} + +fn collect_module_paths( + config: &TomlAppServiceConfig, + modules_dir: PathBuf, +) -> Vec<(&str, PathBuf)> { + config + .toml_faas_config + .module + .iter() + .map(|m| { + let module_file_name = m.file_name.as_ref().unwrap_or_else(|| &m.name); + let module_file_name = PathBuf::from(module_file_name); + // TODO: is it correct to always have .wasm extension? + let module_path = modules_dir.join(module_file_name).with_extension("wasm"); + + (m.name.as_str(), module_path) + }) + .collect::>() +} + +/// Tries to determine a dir with compiled Wasm modules according to the following rules: +/// - if the modules_dir attribute is specified (by user) it will be chosen, +/// - otherwise if modules_dir is specified in AppService config it will be chosen, +/// - otherwise None will be returned. +pub(super) fn resolve_modules_dir( + config: &TomlAppServiceConfig, + modules_dir: Option, +) -> Option { + match modules_dir { + Some(modules_dir) => Some(PathBuf::from(modules_dir)), + None => config + .toml_faas_config + .modules_dir + .as_ref() + .map(|p| PathBuf::from(p)), + } +} diff --git a/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs b/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs new file mode 100644 index 0000000..b739529 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::attributes::FCETestAttributes; +use crate::TResult; +use crate::fce_test::glue_code_generator::generate_test_glue_code; + +use proc_macro2::TokenStream; +use darling::FromMeta; +use syn::parse::Parser; + +pub fn fce_test_impl(attrs: TokenStream, input: TokenStream) -> TResult { + // from https://github.com/dtolnay/syn/issues/788 + let parser = syn::punctuated::Punctuated::::parse_terminated; + let attrs = parser.parse2(attrs)?; + let attrs: Vec = attrs.into_iter().collect(); + let attrs = FCETestAttributes::from_list(&attrs)?; + + let func_item = syn::parse2::(input)?; + + generate_test_glue_code(func_item, attrs) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs b/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs new file mode 100644 index 0000000..bdc7dc5 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs @@ -0,0 +1,197 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::attributes::FCETestAttributes; +use crate::TResult; +use crate::TestGeneratorError; +use crate::fce_test; +use crate::fce_test::config_utils; + +use fluence_app_service::TomlAppServiceConfig; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; + +use std::path::PathBuf; + +/// Generates glue code for tests. +/// F.e. for this test for the greeting service +///```ignore +/// #[fce_test( +/// config_path = "/path/to/service/config/Config.toml", +/// modules_dir = "/path/to/modules/dir" +/// )] +/// fn test() { +/// let result = greeting.greeting("John".to_string()); +/// assert_eq(result.as_str(), "Hi, John!"); +/// } +/// ``` +/// +/// the following glue code would be generated: +///```ignore +/// // (0) +/// pub mod __fce_generated_greeting { +/// struct FCEGeneratedStructgreeting { +/// fce: std::rc::Rc>, +/// } +/// +/// impl FCEGeneratedStructgreeting { +/// pub fn new(fce: std::rc::Rc>) -> Self { +/// Self { fce } +/// } +/// +/// pub fn greeting(&mut self, name: String) -> String { +/// use std::ops::DerefMut; +/// let arguments = fluence_test::internal::json!([name]); +/// let result = self +/// .fce +/// .as_ref +/// .borrow_mut() +/// .call_with_module_name("greeting", "greeting", arguments, <_>::default()) +/// .expect("call to FCE failed"); +/// let result: String = serde_json::from_value(result) +/// .expect("the default deserializer shouldn't fail"); +/// result +/// } +/// } +///} +/// // (1) +/// let tmp_dir = std::env::temp_dir(); +/// let service_id = fluence_test::internal::Uuid::new_v4().to_string(); +/// +/// let tmp_dir = tmp_dir.join(&service_id); +/// let tmp_dir = tmp_dir.to_string_lossy().to_string(); +/// std::fs::create_dir(&tmp_dir).expect("can't create a directory for service in tmp"); +/// +/// let mut __fce__generated_fce_config = fluence_test::internal::TomlAppServiceConfig::load("/path/to/greeting/Config.toml".to_string()) +/// .unwrap_or_else(|e| { +/// panic!( +/// "app service located at `{}` config can't be loaded: {}", +/// "/path/to/greeting/Config.toml", e +/// ) +/// }); +/// +/// __fce__generated_fce_config.service_base_dir = Some("/path/to/tmp".to_string()); +/// +/// let fce = fluence_test::internal::AppService::new_with_empty_facade( +/// __fce__generated_fce_config, +/// "3640e972-92e3-47cb-b95f-4e3c5bcf0f14", +/// std::collections::HashMap::new(), +/// ).unwrap_or_else(|e| panic!("app service can't be created: {}", e)); +/// +/// let fce = std::rc::Rc::new(std::cell::RefCell::new(fce)); +/// +/// // (2) +/// +/// let mut greeting = __fce_generated_greeting::FCEGeneratedStructgreeting::new(fce); +/// +/// // (3) +/// +/// let result = greeting.greeting("John".to_string()); +/// assert_eq(result.as_str(), "Hi, John!"); +/// +/// // (4) +///``` +/// +/// Example code above corresponds to the macro definition in the following way: +/// [(0), (1)] - module_definitions* +/// [(1), (2)] - app_service_ctor +/// [(2), (3)] - module_ctors* +/// [(3), (4)] - original_block +pub(super) fn generate_test_glue_code( + func_item: syn::ItemFn, + attrs: FCETestAttributes, +) -> TResult { + let fce_config = TomlAppServiceConfig::load(&attrs.config_path)?; + let modules_dir = match config_utils::resolve_modules_dir(&fce_config, attrs.modules_dir) { + Some(modules_dir) => modules_dir, + None => return Err(TestGeneratorError::ModulesDirUnspecified), + }; + + let app_service_ctor = generate_app_service_ctor(&attrs.config_path, &modules_dir); + let module_interfaces = fce_test::config_utils::collect_modules(&fce_config, modules_dir)?; + + let module_definitions = + fce_test::module_generator::generate_module_definitions(module_interfaces.iter())?; + + let module_iter = module_interfaces.iter().map(|module| module.name); + let module_ctors = generate_module_ctors(module_iter)?; + + let original_block = func_item.block; + let signature = func_item.sig; + + let glue_code = quote! { + #[test] + #signature { + // definitions for wasm modules specified in config + #(#module_definitions)* + + // AppService constructor and instantiation to implicit `fce` variable + #app_service_ctor + + // constructors of all modules of the tested service + #(#module_ctors)* + + // original test function as is + #original_block + } + }; + + Ok(glue_code) +} + +fn generate_app_service_ctor(config_path: &str, modules_dir: &PathBuf) -> TokenStream { + let config_path = config_path.to_token_stream(); + let modules_dir = modules_dir.to_string_lossy().to_string(); + + quote! { + let tmp_dir = std::env::temp_dir(); + let service_id = fluence_test::internal::Uuid::new_v4().to_string(); + + let tmp_dir = tmp_dir.join(&service_id); + let tmp_dir = tmp_dir.to_string_lossy().to_string(); + std::fs::create_dir(&tmp_dir).expect("can't create a directory for service in tmp"); + + let mut __fce_generated_fce_config = fluence_test::internal::TomlAppServiceConfig::load(#config_path.to_string()) + .unwrap_or_else(|e| panic!("app service located at `{}` config can't be loaded: {}", #config_path, e)); + __fce_generated_fce_config.service_base_dir = Some(tmp_dir); + __fce_generated_fce_config.toml_faas_config.modules_dir = Some(#modules_dir.to_string()); + + let fce = fluence_test::internal::AppService::new_with_empty_facade(__fce_generated_fce_config, service_id, std::collections::HashMap::new()) + .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); + + let fce = std::rc::Rc::new(std::cell::RefCell::new(fce)); + } +} + +fn generate_module_ctors<'n>( + module_names: impl ExactSizeIterator, +) -> TResult> { + module_names + .map(|name| -> TResult<_> { + // TODO: optimize these two call because they are called twice for each module name + // and internally allocate memory in format call. + let module_name = fce_test::utils::generate_module_name(&name)?; + let struct_name = fce_test::utils::generate_struct_name(&name)?; + let name_for_user = fce_test::utils::new_ident(&name)?; + + let module_ctor = + quote! { let mut #name_for_user = #module_name::#struct_name::new(fce.clone()); }; + + Ok(module_ctor) + }) + .collect::>() +} diff --git a/crates/fce-test-macro-impl/src/fce_test/mod.rs b/crates/fce-test-macro-impl/src/fce_test/mod.rs new file mode 100644 index 0000000..492c7af --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod config_utils; +mod fce_test_impl; +mod glue_code_generator; +mod module_generator; +mod utils; + +pub use fce_test_impl::fce_test_impl; diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator.rs new file mode 100644 index 0000000..c29ea00 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator.rs @@ -0,0 +1,99 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod methods_generator; +mod record_type_generator; + +use crate::fce_test::utils; +use crate::fce_test::config_utils::Module; +use crate::TResult; + +use proc_macro2::TokenStream; +use quote::quote; + +/// Generates definitions of modules and records of this modules. +/// F.e. for the greeting service the following definitions would be generated: +///```ignore +/// pub mod __fce_generated_greeting { +/// struct FCEGeneratedStructgreeting { +/// fce: std::rc::Rc>, +/// } +/// +/// impl FCEGeneratedStructgreeting { +/// pub fn new(fce: std::rc::Rc>) -> Self { +/// Self { fce } +/// } +/// +/// pub fn greeting(&mut self, name: String) -> String { +/// use std::ops::DerefMut; +/// let arguments = fluence_test::internal::json!([name]); +/// let result = self +/// .fce +/// .as_ref +/// .borrow_mut() +/// .call_with_module_name("greeting", "greeting", arguments, <_>::default()) +/// .expect("call to FCE failed"); +/// let result: String = serde_json::from_value(result) +/// .expect("the default deserializer shouldn't fail"); +/// result +/// } +/// } +/// } +///``` +pub(super) fn generate_module_definitions<'i>( + modules: impl ExactSizeIterator>, +) -> TResult> { + modules + .into_iter() + .map(generate_module_definition) + .collect::>>() +} + +fn generate_module_definition(module: &Module<'_>) -> TResult { + let module_name = module.name; + let module_name_ident = utils::generate_module_name(module_name)?; + let struct_name_ident = utils::generate_struct_name(module_name)?; + + let module_interface = &module.interface; + let module_records = record_type_generator::generate_records(&module_interface.record_types)?; + let module_functions = methods_generator::generate_module_methods( + module_name, + module_interface.function_signatures.iter(), + &module_interface.record_types, + )?; + + let module_definition = quote! { + pub mod #module_name_ident { + #(#module_records)* + + pub struct #struct_name_ident { + fce: std::rc::Rc>, + } + + impl #struct_name_ident { + pub fn new(fce: std::rc::Rc>) -> Self { + Self { fce } + } + } + + impl #struct_name_ident { + #(#module_functions)* + } + } + }; + + Ok(module_definition) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs new file mode 100644 index 0000000..bdc2a47 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs @@ -0,0 +1,162 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::fce_test::utils; +use crate::TResult; +use crate::TestGeneratorError; + +use fce_wit_parser::interface::it::IType; +use fce_wit_parser::interface::it::IFunctionArg; +use fce_wit_parser::interface::FCERecordTypes; +use fce_wit_parser::interface::FCEFunctionSignature; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_module_methods<'m, 'r>( + module_name: &str, + method_signatures: impl ExactSizeIterator, + records: &'r FCERecordTypes, +) -> TResult> { + method_signatures + .map(|signature| -> TResult<_> { + let func_name = utils::new_ident(&signature.name)?; + let arguments = generate_arguments(signature.arguments.iter(), records)?; + let output_type = generate_output_type(&signature.outputs, records)?; + let fce_call = generate_fce_call(module_name, &signature, records)?; + + let module_method = quote! { + pub fn #func_name(&mut self, #(#arguments),*) #output_type { + #fce_call + } + }; + + Ok(module_method) + }) + .collect::>>() +} + +fn generate_fce_call( + module_name: &str, + method_signature: &FCEFunctionSignature, + records: &FCERecordTypes, +) -> TResult { + let args = method_signature.arguments.iter().map(|a| a.name.as_str()); + let convert_arguments = generate_arguments_converter(args)?; + + let output_type = get_output_type(&method_signature.outputs)?; + let set_result = generate_set_result(&output_type); + let function_call = generate_function_call(module_name, &method_signature.name); + let convert_result_to_output_type = generate_convert_to_output(&output_type, records)?; + let ret = generate_ret(&output_type); + + let function_call = quote! { + use std::ops::DerefMut; + + #convert_arguments + + #set_result #function_call + + #convert_result_to_output_type + + #ret + }; + + Ok(function_call) +} + +/// Generates type convertor to json because of AppService receives them in json. +fn generate_arguments_converter<'a>( + args: impl ExactSizeIterator, +) -> TResult { + let arg_idents: Vec = args.map(utils::new_ident).collect::>()?; + + let args_converter = + quote! { let arguments = fluence_test::internal::json!([#(#arg_idents),*]); }; + + Ok(args_converter) +} + +fn generate_function_call(module_name: &str, method_name: &str) -> TokenStream { + quote! { self.fce.as_ref().borrow_mut().call_with_module_name(#module_name, #method_name, arguments, <_>::default()).expect("call to FCE failed"); } +} + +fn generate_set_result(output_type: &Option<&IType>) -> TokenStream { + match output_type { + Some(_) => quote! { let result = }, + None => TokenStream::new(), + } +} + +fn generate_convert_to_output( + output_type: &Option<&IType>, + records: &FCERecordTypes, +) -> TResult { + let result_stream = match output_type { + Some(ty) => { + let ty = utils::itype_to_tokens(ty, records)?; + quote! { + let result: #ty = serde_json::from_value(result).expect("the default deserializer shouldn't fail"); + } + } + None => TokenStream::new(), + }; + + Ok(result_stream) +} + +fn generate_ret(output_type: &Option<&IType>) -> TokenStream { + match output_type { + Some(_) => quote! { result }, + None => TokenStream::new(), + } +} + +fn generate_arguments<'a, 'r>( + arguments: impl ExactSizeIterator, + records: &'r FCERecordTypes, +) -> TResult> { + arguments + .map(|argument| -> TResult<_> { + let arg_name = utils::new_ident(&argument.name)?; + let arg_type = utils::itype_to_tokens(&argument.ty, records)?; + + let arg = quote! { #arg_name: #arg_type }; + Ok(arg) + }) + .collect::>>() +} + +fn generate_output_type(output_types: &[IType], records: &FCERecordTypes) -> TResult { + let output_type = get_output_type(output_types)?; + match output_type { + None => Ok(TokenStream::new()), + Some(ty) => { + let output_type = utils::itype_to_tokens(&ty, records)?; + let output_type = quote! { -> #output_type }; + + Ok(output_type) + } + } +} + +fn get_output_type(output_types: &[IType]) -> TResult> { + match output_types.len() { + 0 => Ok(None), + 1 => Ok(Some(&output_types[0])), + _ => Err(TestGeneratorError::ManyFnOutputsUnsupported), + } +} diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs new file mode 100644 index 0000000..2e20dfd --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::fce_test::utils; +use crate::TResult; + +use fce_wit_parser::interface::it::IRecordFieldType; +use fce_wit_parser::interface::FCERecordTypes; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_records(records: &FCERecordTypes) -> TResult> { + use std::ops::Deref; + + records.iter().map(|(_, record)| -> TResult<_> { + let record_name_ident = utils::generate_record_name(&record.name)?; + let fields = prepare_field(record.fields.deref().iter(), records)?; + + let generated_record = quote! { + #[derive(Clone, fluence_test::internal::Serialize, fluence_test::internal::Deserialize)] + pub struct #record_name_ident { + #(#fields),* + } + }; + + Ok(generated_record) + } + ).collect::>>() +} + +fn prepare_field<'f>( + fields: impl ExactSizeIterator, + records: &FCERecordTypes, +) -> TResult> { + fields + .map(|field| -> TResult<_> { + let field_name = utils::new_ident(&field.name)?; + let field_type = utils::itype_to_tokens(&field.ty, records)?; + + let generated_field = quote! { #field_name: #field_type }; + + Ok(generated_field) + }) + .collect::>>() +} diff --git a/crates/fce-test-macro-impl/src/fce_test/utils.rs b/crates/fce-test-macro-impl/src/fce_test/utils.rs new file mode 100644 index 0000000..a39f823 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/utils.rs @@ -0,0 +1,77 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::TResult; +use fce_wit_parser::interface::FCERecordTypes; +use fce_wit_parser::interface::it::IType; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_module_name(module_name: &str) -> TResult { + let extended_module_name = format!("__fce_generated_{}", module_name); + new_ident(&extended_module_name) +} + +pub(super) fn generate_record_name(record_name: &str) -> TResult { + let extended_record_name = format!("{}", record_name); + new_ident(&extended_record_name) +} + +pub(super) fn generate_struct_name(struct_name: &str) -> TResult { + let extended_struct_name = format!("FCEGeneratedStruct{}", struct_name); + new_ident(&extended_struct_name) +} + +pub(super) fn new_ident(ident_str: &str) -> TResult { + syn::parse_str::(ident_str).map_err(Into::into) +} + +pub(super) fn itype_to_tokens(itype: &IType, records: &FCERecordTypes) -> TResult { + let token_stream = match itype { + IType::Record(record_id) => { + let record = records + .get(record_id) + .ok_or_else(|| crate::errors::CorruptedITSection::AbsentRecord(*record_id))?; + let record_name = new_ident(&record.name)?; + let token_stream = quote! { #record_name }; + token_stream + } + IType::Array(ty) => { + let inner_ty_token_stream = itype_to_tokens(ty, records)?; + let token_stream = quote! { Vec<#inner_ty_token_stream> }; + token_stream + } + IType::String => quote! { String }, + IType::S8 => quote! { i8 }, + IType::S16 => quote! { i16 }, + IType::S32 => quote! { i32 }, + IType::S64 => quote! { i64 }, + IType::U8 => quote! { u8 }, + IType::U16 => quote! { u16 }, + IType::U32 => quote! { u32 }, + IType::U64 => quote! { u64 }, + IType::I32 => quote! { i32 }, + IType::I64 => quote! { i64 }, + IType::F32 => quote! { f32 }, + IType::F64 => quote! { f64 }, + IType::Anyref => { + unimplemented!("anyrefs aren't supported and will be deleted from IType soon") + } + }; + + Ok(token_stream) +} diff --git a/crates/fce-test-macro-impl/src/lib.rs b/crates/fce-test-macro-impl/src/lib.rs new file mode 100644 index 0000000..432d957 --- /dev/null +++ b/crates/fce-test-macro-impl/src/lib.rs @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![deny( + dead_code, + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "1024"] + +mod attributes; +mod errors; +mod fce_test; + +pub use fce_test::fce_test_impl; +pub use errors::TestGeneratorError; + +pub(crate) type TResult = std::result::Result; diff --git a/crates/fce-test-macro/Cargo.toml b/crates/fce-test-macro/Cargo.toml new file mode 100644 index 0000000..c8578ca --- /dev/null +++ b/crates/fce-test-macro/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "fluence-sdk-test-macro" +version = "0.5.0" # remember to update html_root_url +edition = "2018" +description = "Definition of the `#[fce_test]` macro" +repository = "https://github.com/fluencelabs/rust-sdk/crates/macro-test" +authors = ["Fluence Labs"] +keywords = ["fluence", "sdk", "webassembly", "procedural_macros"] +categories = ["api-bindings", "wasm"] +license = "Apache-2.0" + +[package.metadata.docs.rs] +all-features = true + +[lib] +proc-macro = true +doctest = false + +[dependencies] +fluence-sdk-test-macro-impl = { path = "../fce-test-macro-impl", version = "=0.5.0" } + +quote = "1.0.9" +proc-macro2 = "1.0.24" +proc-macro-error = { version = "1.0.4", default-features = false } +syn = { version = '1.0.64', features = ['full'] } diff --git a/crates/fce-test-macro/src/lib.rs b/crates/fce-test-macro/src/lib.rs new file mode 100644 index 0000000..0e6c594 --- /dev/null +++ b/crates/fce-test-macro/src/lib.rs @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.5.0")] +#![deny( + dead_code, + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +#![warn(rust_2018_idioms)] +#![recursion_limit = "1024"] + +use fluence_sdk_test_macro_impl::fce_test_impl; +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use syn::spanned::Spanned; + +/// This macro allows user to write tests for services in the following form: +///```ignore +/// #[fce_test(config = "/path/to/Config.toml", modules_dir = "path/to/service/modules")] +/// fn test() { +/// let service_result = greeting.greeting("John".to_string()); +/// assert_eq!(&service_result, "Hi, name!"); +/// } +///``` +#[proc_macro_error] +#[proc_macro_attribute] +pub fn fce_test(attrs: TokenStream, input: TokenStream) -> TokenStream { + let attrs: proc_macro2::TokenStream = attrs.into(); + let attrs_span = attrs.span(); + + match fce_test_impl(attrs, input.into()) { + Ok(stream) => stream.into(), + Err(e) => proc_macro_error::abort!(attrs_span, format!("{}", e)), + } +} diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml index 7d601b8..0ad43c8 100644 --- a/crates/main/Cargo.toml +++ b/crates/main/Cargo.toml @@ -10,15 +10,16 @@ keywords = ["fluence", "sdk", "webassembly"] categories = ["api-bindings", "wasm"] license = "Apache-2.0" -[package.metadata.docs.rs] # https://docs.rs/about +[package.metadata.docs.rs] all-features = true [lib] path = "src/lib.rs" crate-type = ["rlib"] +doctest = false [dependencies] -fluence-sdk-macro = { path = "../macro", version = "=0.5.0" } +fluence-sdk-macro = { path = "../fce-macro", version = "=0.5.0" } log = { version = "0.4.8", features = ["std"] } serde = "=1.0.118" diff --git a/crates/main/src/lib.rs b/crates/main/src/lib.rs index 22a074a..575368e 100644 --- a/crates/main/src/lib.rs +++ b/crates/main/src/lib.rs @@ -34,7 +34,9 @@ mod export_allocator; #[cfg(any(feature = "debug", feature = "logger"))] mod logger; +mod module_manifest; mod result; +mod sdk_version_embedder; pub use export_allocator::allocate; pub use export_allocator::deallocate; @@ -53,6 +55,9 @@ pub use result::set_result_size; pub use result::release_objects; pub use result::add_object_to_release; +pub use module_manifest::MANIFEST_SECTION_NAME; +pub use sdk_version_embedder::VERSION_SECTION_NAME; + #[allow(unused_variables)] pub(crate) fn log>(msg: S) { // logs will be printed only if debug feature is enabled diff --git a/crates/main/src/module_manifest.rs b/crates/main/src/module_manifest.rs new file mode 100644 index 0000000..003864e --- /dev/null +++ b/crates/main/src/module_manifest.rs @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: avoid duplication with the link_section when key-value attributes become stable +pub const MANIFEST_SECTION_NAME: &str = "__fluence_wasm_module_manifest"; + +#[macro_export] +macro_rules! module_manifest { + ($authors:expr, $version:expr, $description:expr, $repository:expr) => { + const __FCE_SDK_AUTHORS_SIZE: usize = $authors.as_bytes().len(); + const __FCE_SDK_VERSION_SIZE: usize = $version.as_bytes().len(); + const __FCE_SDK_DESCRIPTION_SIZE: usize = $description.as_bytes().len(); + const __FCE_SDK_REPOSITORY_SIZE: usize = $repository.as_bytes().len(); + const __FCE_SDK_FIELD_PREFIX_SIZE: usize = std::mem::size_of::(); + + const __FCE_MANIFEST_SIZE: usize = __FCE_SDK_AUTHORS_SIZE + + __FCE_SDK_VERSION_SIZE + + __FCE_SDK_DESCRIPTION_SIZE + + __FCE_SDK_REPOSITORY_SIZE + + __FCE_SDK_FIELD_PREFIX_SIZE * 4; + + const fn __fce_sdk_append_data( + mut manifest: [u8; __FCE_MANIFEST_SIZE], + data: &'static str, + offset: usize, + ) -> ([u8; __FCE_MANIFEST_SIZE], usize) { + let data_as_bytes = data.as_bytes(); + let data_len = data_as_bytes.len(); + + // write data prefix with data size in LE + let data_len_u64 = data_len as u64; + let data_len_le_bytes = data_len_u64.to_le_bytes(); + let mut byte_idx = 0; + while byte_idx < __FCE_SDK_FIELD_PREFIX_SIZE { + manifest[offset + byte_idx] = data_len_le_bytes[byte_idx]; + byte_idx += 1; + } + + // write data + let mut byte_idx = 0; + while byte_idx < data_len { + manifest[__FCE_SDK_FIELD_PREFIX_SIZE + offset + byte_idx] = data_as_bytes[byte_idx]; + byte_idx += 1; + } + + (manifest, offset + __FCE_SDK_FIELD_PREFIX_SIZE + data_len) + } + + const fn generate_manifest() -> [u8; __FCE_MANIFEST_SIZE] { + let manifest: [u8; __FCE_MANIFEST_SIZE] = [0; __FCE_MANIFEST_SIZE]; + + let offset = 0; + let (manifest, offset) = __fce_sdk_append_data(manifest, $authors, offset); + let (manifest, offset) = __fce_sdk_append_data(manifest, $version, offset); + let (manifest, offset) = __fce_sdk_append_data(manifest, $description, offset); + let (manifest, _) = __fce_sdk_append_data(manifest, $repository, offset); + + manifest + } + + #[cfg(target_arch = "wasm32")] + #[link_section = "__fluence_wasm_module_manifest"] + #[doc(hidden)] + pub static __FCE_WASM_MODULE_MANIFEST: [u8; __FCE_MANIFEST_SIZE] = generate_manifest(); + }; + + () => { + module_manifest!( + env!("CARGO_PKG_AUTHORS"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_DESCRIPTION"), + env!("CARGO_PKG_REPOSITORY") + ); + }; +} diff --git a/crates/main/src/sdk_version_embedder.rs b/crates/main/src/sdk_version_embedder.rs new file mode 100644 index 0000000..57c2071 --- /dev/null +++ b/crates/main/src/sdk_version_embedder.rs @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![allow(dead_code)] + +const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); +const VERSION_SIZE: usize = PKG_VERSION.len(); + +const fn sdk_version() -> [u8; VERSION_SIZE] { + let version_as_slice = PKG_VERSION.as_bytes(); + + let mut version_as_array: [u8; VERSION_SIZE] = [0; VERSION_SIZE]; + let mut byte_id = 0; + while byte_id < VERSION_SIZE { + version_as_array[byte_id] = version_as_slice[byte_id]; + byte_id += 1; + } + + version_as_array +} + +// TODO: avoid duplication with the link_section when key-value attributes become stable +pub const VERSION_SECTION_NAME: &str = "__fluence_sdk_version"; + +#[cfg(target_arch = "wasm32")] +#[link_section = "__fluence_sdk_version"] +#[doc(hidden)] +pub static __FCE_SDK_VERSION: [u8; VERSION_SIZE] = sdk_version(); diff --git a/crates/wit/Cargo.toml b/crates/wit/Cargo.toml index 791c5d0..cd47583 100644 --- a/crates/wit/Cargo.toml +++ b/crates/wit/Cargo.toml @@ -10,13 +10,13 @@ keywords = ["fluence", "sdk", "webassembly", "wit", "interface-types"] categories = ["api-bindings", "wasm"] license = "Apache-2.0" -[package.metadata.docs.rs] # https://docs.rs/about +[package.metadata.docs.rs] all-features = true [dependencies] -quote = "1.0.7" -proc-macro2 = "1.0.18" +quote = "1.0.9" +proc-macro2 = "1.0.24" serde = { version = "=1.0.118", features = ["derive"] } serde_json = "1.0.56" -syn = { version = '1.0.33', features = ['full'] } -uuid = { version = "0.8.1", features = ["v4"] } +syn = { version = '1.0.64', features = ['full'] } +uuid = { version = "0.8.2", features = ["v4"] } diff --git a/crates/wit/src/parsed_type.rs b/crates/wit/src/parsed_type.rs index 4b6425c..bc6305c 100644 --- a/crates/wit/src/parsed_type.rs +++ b/crates/wit/src/parsed_type.rs @@ -99,7 +99,7 @@ impl ParsedType { } _ if !type_segment.arguments.is_empty() => Err(Error::new( type_segment.span(), - "type with lifetimes or generics aren't allowed".to_string(), + "types with lifetimes or generics aren't allowed".to_string(), )), _ => Ok(ParsedType::Record( (&type_segment.ident).into_token_stream().to_string(), diff --git a/crates/wit/src/parsed_type/fn_epilog.rs b/crates/wit/src/parsed_type/fn_epilog.rs index 3446368..65404bb 100644 --- a/crates/wit/src/parsed_type/fn_epilog.rs +++ b/crates/wit/src/parsed_type/fn_epilog.rs @@ -31,7 +31,7 @@ pub(crate) struct FnEpilogDescriptor { /// This trait could be used to generate various parts needed to construct epilog of an export /// function. They are marked with # in the following example: -/// ``` +/// ```ignore /// quote! { /// pub unsafe fn foo(...) #fn_return_type { /// ... diff --git a/crates/wit/src/parsed_type/fn_prolog.rs b/crates/wit/src/parsed_type/fn_prolog.rs index 6ce53b3..6308845 100644 --- a/crates/wit/src/parsed_type/fn_prolog.rs +++ b/crates/wit/src/parsed_type/fn_prolog.rs @@ -34,7 +34,7 @@ pub(crate) struct FnPrologDescriptor { /// This trait could be used to generate various parts needed to construct prolog of an export /// function. They are marked with # in the following example: -/// ``` +/// ```ignore /// quote! { /// fn foo(#(#raw_arg_names: #raw_arg_types),*) { /// #prolog diff --git a/crates/wit/src/parsed_type/foreign_mod_prolog.rs b/crates/wit/src/parsed_type/foreign_mod_prolog.rs index 476a1c5..d56f51a 100644 --- a/crates/wit/src/parsed_type/foreign_mod_prolog.rs +++ b/crates/wit/src/parsed_type/foreign_mod_prolog.rs @@ -34,7 +34,7 @@ pub(crate) struct ExternDescriptor { /// This trait could be used to generate various parts needed to construct prolog of an wrapper /// function or extern block. They are marked with # in the following examples: -/// ``` +/// ```ignore /// quote! { /// fn foo(#(#arg_names: #arg_types), *) { /// let arg_1 = std::mem::ManuallyDrop::new(arg_1); @@ -45,7 +45,7 @@ pub(crate) struct ExternDescriptor { /// } /// ``` /// -/// ``` +/// ```ignore /// quote! { /// extern "C" { /// #[link_name = "foo_link_name"] diff --git a/fluence-test/Cargo.toml b/fluence-test/Cargo.toml new file mode 100644 index 0000000..76c4596 --- /dev/null +++ b/fluence-test/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "fluence-test" +version = "0.5.0" # remember to update html_root_url +description = "Fluence backend SDK for testing" +documentation = "https://docs.rs/fluence/" +repository = "https://github.com/fluencelabs/rust-sdk" +authors = ["Fluence Labs"] +readme = "README.md" +keywords = ["fluence", "sdk", "webassembly"] +categories = ["api-bindings", "wasm"] +license = "Apache-2.0" +edition = "2018" + +[package.metadata.docs.rs] +all-features = true + +[lib] +path = "src/lib.rs" +doctest = false + +[dependencies] +fluence-sdk-test-macro = { path = "../crates/fce-test-macro", version = "=0.5.0" } +fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] } + +serde = { version = "1.0.118", features = ["derive"] } +serde_json = "1.0.64" +uuid = { version = "0.8.2", features = ["v4"] } diff --git a/fluence-test/src/lib.rs b/fluence-test/src/lib.rs new file mode 100644 index 0000000..c60b3fc --- /dev/null +++ b/fluence-test/src/lib.rs @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![doc(html_root_url = "https://docs.rs/fluence-test/0.5.0")] +#![deny( + dead_code, + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +#![warn(rust_2018_idioms)] + +pub use fluence_sdk_test_macro::fce_test; + +/// These API functions are intended for internal usage in generated code. +/// Normally, you shouldn't use them. +pub mod internal { + pub use fluence_app_service::AppService; + pub use fluence_app_service::TomlAppServiceConfig; + + pub use serde::Serialize; + pub use serde::Deserialize; + pub use serde_json::json; + + pub use uuid::Uuid; +} diff --git a/fluence/Cargo.toml b/fluence/Cargo.toml new file mode 100644 index 0000000..f53ae5c --- /dev/null +++ b/fluence/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "fluence" +version = "0.5.0" # remember to update html_root_url +description = "Fluence backend SDK for developing backend applications for the Fluence network" +documentation = "https://docs.rs/fluence/" +repository = "https://github.com/fluencelabs/rust-sdk" +authors = ["Fluence Labs"] +readme = "README.md" +keywords = ["fluence", "sdk", "webassembly"] +categories = ["api-bindings", "wasm"] +license = "Apache-2.0" +edition = "2018" + +[package.metadata.docs.rs] +all-features = true + +[lib] +path = "src/lib.rs" +doctest = false + +[dependencies] +fluence-sdk-macro = { path = "../crates/fce-macro", version = "=0.5.0" } +fluence-sdk-main = { path = "../crates/main", version = "=0.5.0" } + +serde = { version = "1.0.118", features = ["derive"]} + +[dev-dependencies] +trybuild = "1.0" + +[features] +# Print some internal logs by log_utf8_string +debug = ["fluence-sdk-main/debug"] + +# Enable logger (this will cause log_utf8_string to appear in imports) +logger = ["fluence-sdk-main/logger"] diff --git a/src/call_parameters.rs b/fluence/src/call_parameters.rs similarity index 100% rename from src/call_parameters.rs rename to fluence/src/call_parameters.rs diff --git a/src/lib.rs b/fluence/src/lib.rs similarity index 95% rename from src/lib.rs rename to fluence/src/lib.rs index fe31c00..1cb4eb5 100644 --- a/src/lib.rs +++ b/fluence/src/lib.rs @@ -70,9 +70,11 @@ mod call_parameters; mod mounted_binary; -extern crate self as fluence; +// extern crate self as fluence; pub use fluence_sdk_macro::fce; +#[cfg(feature = "fce-test")] +pub use fluence_sdk_test_macro::fce_test; pub use call_parameters::CallParameters; pub use call_parameters::SecurityTetraplet; @@ -88,6 +90,8 @@ pub use mounted_binary::Result as MountedBinaryResult; pub use mounted_binary::StringResult as MountedBinaryStringResult; pub use mounted_binary::SUCCESS_CODE as BINARY_SUCCESS_CODE; +pub use fluence_sdk_main::module_manifest; + /// These API functions are intended for internal usage in generated code. /// Normally, you shouldn't use them. pub mod internal { diff --git a/src/mounted_binary.rs b/fluence/src/mounted_binary.rs similarity index 100% rename from src/mounted_binary.rs rename to fluence/src/mounted_binary.rs diff --git a/fluence/tests/export_functions/arrays.rs b/fluence/tests/export_functions/arrays.rs new file mode 100644 index 0000000..2b9c7f4 --- /dev/null +++ b/fluence/tests/export_functions/arrays.rs @@ -0,0 +1,67 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +pub fn main() {} + +#[fce] +pub fn byte_type( __arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn inner_arrays_1(_arg: Vec>>>) -> Vec>>> { + unimplemented!() +} + +#[fce] +#[derive(Default)] +pub struct TestRecord { + pub field_0: i32, + pub field_1: Vec>, +} + +#[fce] +pub fn inner_arrays_2(_arg: Vec>>>) -> Vec>>> { + unimplemented!() +} + +#[fce] +pub fn string_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn f32_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn f64_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn u32_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn u64_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn i32_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn i64_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn empty_type() -> Vec { + unimplemented!() +} diff --git a/fluence/tests/export_functions/basic_types.rs b/fluence/tests/export_functions/basic_types.rs new file mode 100644 index 0000000..b0cd5fb --- /dev/null +++ b/fluence/tests/export_functions/basic_types.rs @@ -0,0 +1,73 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +pub fn main() {} + +#[fce] +pub fn all_types( + _arg_0: i8, + _arg_1: i16, + _arg_2: i32, + _arg_3: i64, + _arg_4: u8, + _arg_5: u16, + _arg_6: u32, + _arg_7: u64, + _arg_8: f32, + _arg_9: f64, + _arg_10: String, + _arg_11: Vec, +) -> Vec { + unimplemented!() +} + +#[fce] +pub fn string_type(_arg: String) -> String { + unimplemented!() +} + +#[fce] +pub fn bytearray_type(_arg: Vec) -> Vec { + unimplemented!() +} + +#[fce] +pub fn bool_type(_arg: bool) -> bool { + unimplemented!() +} + +#[fce] +pub fn f32_type(_arg: f32) -> f32 { + unimplemented!() +} + +#[fce] +pub fn f64_type(_arg: f64) -> f64 { + unimplemented!() +} + +#[fce] +pub fn u32_type(_arg: u32) -> u32 { + unimplemented!() +} + +#[fce] +pub fn u64_type(_arg: u64) -> u64 { + unimplemented!() +} + +#[fce] +pub fn i32_type(_arg: i32) -> i32 { + unimplemented!() +} + +#[fce] +pub fn i64_type(_arg: i64) -> i64 { + unimplemented!() +} + +#[fce] +pub fn empty_type() -> String { + unimplemented!() +} diff --git a/fluence/tests/export_functions/improper_types.rs b/fluence/tests/export_functions/improper_types.rs new file mode 100644 index 0000000..d2caa86 --- /dev/null +++ b/fluence/tests/export_functions/improper_types.rs @@ -0,0 +1,24 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +pub fn main() {} + +#[fce] +fn test(_arg_1: Box) {} + +#[fce] +fn test2(_arg_1: std::rc::Rc) {} + +#[fce] +fn test3(_arg_1: std::collections::HashMap) {} + +#[fce] +fn test4(_arg_1: i32) -> (i32, i32) { + unimplemented!() +} + +#[fce] +fn test5(_arg_1: i32) -> Box { + unimplemented!() +} diff --git a/fluence/tests/export_functions/improper_types.stderr b/fluence/tests/export_functions/improper_types.stderr new file mode 100644 index 0000000..484a611 --- /dev/null +++ b/fluence/tests/export_functions/improper_types.stderr @@ -0,0 +1,29 @@ +error: types with lifetimes or generics aren't allowed + --> $DIR/improper_types.rs:8:17 + | +8 | fn test(_arg_1: Box) {} + | ^^^^^^^^ + +error: types with lifetimes or generics aren't allowed + --> $DIR/improper_types.rs:11:27 + | +11 | fn test2(_arg_1: std::rc::Rc) {} + | ^^^^^^^ + +error: types with lifetimes or generics aren't allowed + --> $DIR/improper_types.rs:14:36 + | +14 | fn test3(_arg_1: std::collections::HashMap) {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: Incorrect argument type - passing only by value is supported now + --> $DIR/improper_types.rs:17:26 + | +17 | fn test4(_arg_1: i32) -> (i32, i32) { + | ^^^^^^^^^^ + +error: types with lifetimes or generics aren't allowed + --> $DIR/improper_types.rs:22:26 + | +22 | fn test5(_arg_1: i32) -> Box { + | ^^^^^^^^ diff --git a/fluence/tests/import_functions/arrays.rs b/fluence/tests/import_functions/arrays.rs new file mode 100644 index 0000000..052a884 --- /dev/null +++ b/fluence/tests/import_functions/arrays.rs @@ -0,0 +1,40 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +pub fn main() {} + +#[fce] +#[derive(Default)] +pub struct TestRecord { + pub field_0: i32, + pub field_1: Vec>, +} + +#[fce] +#[link(wasm_import_module = "arrays_passing_effector")] +extern "C" { + pub fn inner_arrays_1(arg: Vec>>>) -> Vec>>>; + + pub fn inner_arrays_2( + arg: Vec>>>, + ) -> Vec>>>; + + pub fn string_type(arg: Vec) -> Vec; + + pub fn byte_type(arg: Vec) -> Vec; + + pub fn f32_type(arg: Vec) -> Vec; + + pub fn f64_type(arg: Vec) -> Vec; + + pub fn u32_type(arg: Vec) -> Vec; + + pub fn u64_type(arg: Vec) -> Vec; + + pub fn i32_type(arg: Vec) -> Vec; + + pub fn i64_type(arg: Vec) -> Vec; + + pub fn empty_type() -> Vec; +} diff --git a/fluence/tests/import_functions/basic_types.rs b/fluence/tests/import_functions/basic_types.rs new file mode 100644 index 0000000..340a5d2 --- /dev/null +++ b/fluence/tests/import_functions/basic_types.rs @@ -0,0 +1,40 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +fn main() {} + +#[fce] +#[link(wasm_import_module = "arguments_passing_effector")] +extern "C" { + pub fn all_types( + arg_0: i8, + arg_1: i16, + arg_2: i32, + arg_3: i64, + arg_4: u8, + arg_5: u16, + arg_6: u32, + arg_7: u64, + arg_8: f32, + arg_9: f64, + arg_10: String, + arg_11: Vec, + ) -> Vec; + + pub fn string_type(arg: String) -> String; + pub fn bytearray_type(arg: Vec) -> Vec; + + pub fn bool_type(arg: bool) -> bool; + + pub fn f32_type(arg: f32) -> f32; + pub fn f64_type(arg: f64) -> f64; + + pub fn u32_type(arg: u32) -> u32; + pub fn u64_type(arg: u64) -> u64; + + pub fn i32_type(arg: i32) -> i32; + pub fn i64_type(arg: i64) -> i64; + + pub fn empty_type() -> String; +} diff --git a/fluence/tests/import_functions/improper_types.rs b/fluence/tests/import_functions/improper_types.rs new file mode 100644 index 0000000..5888828 --- /dev/null +++ b/fluence/tests/import_functions/improper_types.rs @@ -0,0 +1,24 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +pub fn main() {} + +#[fce] +#[link(wasm_import_module = "arguments_passing_effector")] +extern "C" { + #[fce] + fn test(_arg_1: Box); + + #[fce] + fn test2(_arg_1: std::rc::Rc); + + #[fce] + fn test3(_arg_1: std::collections::HashMap); + + #[fce] + fn test4(_arg_1: i32) -> (i32, i32); + + #[fce] + fn test5(_arg_1: i32) -> Box; +} diff --git a/fluence/tests/import_functions/improper_types.stderr b/fluence/tests/import_functions/improper_types.stderr new file mode 100644 index 0000000..af4f71d --- /dev/null +++ b/fluence/tests/import_functions/improper_types.stderr @@ -0,0 +1,5 @@ +error: types with lifetimes or generics aren't allowed + --> $DIR/improper_types.rs:11:21 + | +11 | fn test(_arg_1: Box); + | ^^^^^^^^ diff --git a/fluence/tests/records/basic_structs.rs b/fluence/tests/records/basic_structs.rs new file mode 100644 index 0000000..34d8b79 --- /dev/null +++ b/fluence/tests/records/basic_structs.rs @@ -0,0 +1,82 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +fn main() {} + +#[fce] +pub struct TestRecord { + pub field_0: bool, + pub field_1: i8, + pub field_2: i16, + pub field_3: i32, + pub field_4: i64, + pub field_5: u8, + pub field_6: u16, + pub field_7: u32, + pub field_8: u64, + pub field_9: f32, + pub field_10: f64, + pub field_11: String, + pub field_12: Vec, +} + +#[fce] +pub struct Tx { + pub block_hash: String, + pub block_number: String, + pub from: String, + pub gas: String, + pub gas_price: String, + pub hash: String, + pub input: String, + pub nonce: String, + pub to: String, + pub transaction_index: String, + pub value: String, +} + +#[fce] +#[derive(Debug)] +pub struct JsonRpcResult { + pub json_rpc: String, + pub result: String, + pub error: String, + pub id: u64, +} + +#[fce] +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct User { + pub peer_id: String, + pub relay_id: String, + pub signature: String, + pub name: String, +} + +#[fce] +pub struct GetUsersServiceResult { + pub ret_code: i32, + pub err_msg: String, + pub users: Vec, +} + +#[fce] +pub struct EmptyServiceResult { + pub ret_code: i32, + pub err_msg: String, +} + +#[fce] +pub struct ExistsServiceResult { + pub ret_code: i32, + pub err_msg: String, + pub is_exists: bool, +} + +#[fce] +pub struct AuthResult { + pub ret_code: i32, + pub err_msg: String, + pub is_authenticated: bool, +} diff --git a/fluence/tests/records/empty_struct.rs b/fluence/tests/records/empty_struct.rs new file mode 100644 index 0000000..7a8bce1 --- /dev/null +++ b/fluence/tests/records/empty_struct.rs @@ -0,0 +1,8 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +fn main() {} + +#[fce] +struct A {} diff --git a/fluence/tests/records/struct_with_improper_types.rs b/fluence/tests/records/struct_with_improper_types.rs new file mode 100644 index 0000000..40889a4 --- /dev/null +++ b/fluence/tests/records/struct_with_improper_types.rs @@ -0,0 +1,20 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +fn main() {} + +#[fce] +struct StructWithBox { + pub a: Box, +} + +#[fce] +struct StructWithRc { + pub a: std::rc::Rc, +} + +#[fce] +struct StructWithHashMap { + pub a: std::collections::HashMap, +} diff --git a/fluence/tests/records/struct_with_improper_types.stderr b/fluence/tests/records/struct_with_improper_types.stderr new file mode 100644 index 0000000..c6584d4 --- /dev/null +++ b/fluence/tests/records/struct_with_improper_types.stderr @@ -0,0 +1,17 @@ +error: types with lifetimes or generics aren't allowed + --> $DIR/struct_with_improper_types.rs:9:12 + | +9 | pub a: Box, + | ^^^^^^^^ + +error: types with lifetimes or generics aren't allowed + --> $DIR/struct_with_improper_types.rs:14:21 + | +14 | pub a: std::rc::Rc, + | ^^^^^^^ + +error: types with lifetimes or generics aren't allowed + --> $DIR/struct_with_improper_types.rs:19:30 + | +19 | pub a: std::collections::HashMap, + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/fluence/tests/records/struct_with_private_fields.rs b/fluence/tests/records/struct_with_private_fields.rs new file mode 100644 index 0000000..ca4f2aa --- /dev/null +++ b/fluence/tests/records/struct_with_private_fields.rs @@ -0,0 +1,11 @@ +#![allow(improper_ctypes)] + +use fluence::fce; + +fn main() {} + +#[fce] +struct StructWithPrivateFields { + a: i32, + b: usize, +} diff --git a/fluence/tests/records/struct_with_private_fields.stderr b/fluence/tests/records/struct_with_private_fields.stderr new file mode 100644 index 0000000..22b2d15 --- /dev/null +++ b/fluence/tests/records/struct_with_private_fields.stderr @@ -0,0 +1,5 @@ +error: #[fce] could be applied only to struct with all public fields + --> $DIR/struct_with_private_fields.rs:9:5 + | +9 | a: i32, + | ^^^^^^ diff --git a/fluence/tests/test_runner.rs b/fluence/tests/test_runner.rs new file mode 100644 index 0000000..ff32c6d --- /dev/null +++ b/fluence/tests/test_runner.rs @@ -0,0 +1,16 @@ +#[test] +fn test() { + let tests = trybuild::TestCases::new(); + tests.pass("tests/export_functions/arrays.rs"); + tests.pass("tests/export_functions/basic_types.rs"); + tests.compile_fail("tests/export_functions/improper_types.rs"); + + tests.pass("tests/import_functions/arrays.rs"); + tests.pass("tests/import_functions/basic_types.rs"); + tests.compile_fail("tests/import_functions/improper_types.rs"); + + tests.pass("tests/records/basic_structs.rs"); + tests.pass("tests/records/empty_struct.rs"); + tests.compile_fail("tests/records/struct_with_improper_types.rs"); + tests.compile_fail("tests/records/struct_with_private_fields.rs"); +}