Merge branch 'master' into tests

This commit is contained in:
vms 2021-04-01 14:53:33 +03:00
commit ae3c6e1e98
28 changed files with 1095 additions and 63 deletions

View File

@ -15,13 +15,17 @@ jobs:
rustup toolchain install nightly-2021-02-27
rustup default nightly-2021-02-27
rustup override set nightly-2021-02-27
rustup target add wasm32-unknown-unknown
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

View File

@ -1,36 +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"
[dependencies]
fluence-sdk-macro = { path = "crates/macro", version = "=0.5.0" }
fluence-sdk-main = { path = "crates/main", version = "=0.5.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"]
[workspace]
members = [
"crates/fce-macro",
"crates/fce-test-macro",
"crates/fce-test-macro-impl",
"crates/main",
"crates/macro",
"crates/wit",
"fluence",
"fluence-test"
]

View File

@ -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,7 +10,7 @@ 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]

View File

@ -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<u8>
//! }
//! ```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<String>) -> MountedBinaryResult;
//! }
//!
//! ```

View File

@ -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"

View File

@ -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<String>,
}

View File

@ -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),
}

View File

@ -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<Vec<Module<'_>>> {
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::<Result<Vec<_>, _>>()
.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::<Vec<_>>()
}
/// 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<String>,
) -> Option<PathBuf> {
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)),
}
}

View File

@ -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<TokenStream> {
// from https://github.com/dtolnay/syn/issues/788
let parser = syn::punctuated::Punctuated::<syn::NestedMeta, syn::Token![,]>::parse_terminated;
let attrs = parser.parse2(attrs)?;
let attrs: Vec<syn::NestedMeta> = attrs.into_iter().collect();
let attrs = FCETestAttributes::from_list(&attrs)?;
let func_item = syn::parse2::<syn::ItemFn>(input)?;
generate_test_glue_code(func_item, attrs)
}

View File

@ -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<std::cell::RefCell<fluence_test::internal::AppService>>,
/// }
///
/// impl FCEGeneratedStructgreeting {
/// pub fn new(fce: std::rc::Rc<std::cell::RefCell<fluence_test::internal::AppService>>) -> 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<TokenStream> {
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<Item = &'n str>,
) -> TResult<Vec<TokenStream>> {
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::<TResult<_>>()
}

View File

@ -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;

View File

@ -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<std::cell::RefCell<fluence_test::internal::AppService>>,
/// }
///
/// impl FCEGeneratedStructgreeting {
/// pub fn new(fce: std::rc::Rc<std::cell::RefCell<fluence_test::internal::AppService>>) -> 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<Item = &'i Module<'i>>,
) -> TResult<Vec<TokenStream>> {
modules
.into_iter()
.map(generate_module_definition)
.collect::<TResult<Vec<_>>>()
}
fn generate_module_definition(module: &Module<'_>) -> TResult<TokenStream> {
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<std::cell::RefCell<fluence_test::internal::AppService>>,
}
impl #struct_name_ident {
pub fn new(fce: std::rc::Rc<std::cell::RefCell<fluence_test::internal::AppService>>) -> Self {
Self { fce }
}
}
impl #struct_name_ident {
#(#module_functions)*
}
}
};
Ok(module_definition)
}

View File

@ -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<Item = &'m FCEFunctionSignature>,
records: &'r FCERecordTypes,
) -> TResult<Vec<TokenStream>> {
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::<TResult<Vec<_>>>()
}
fn generate_fce_call(
module_name: &str,
method_signature: &FCEFunctionSignature,
records: &FCERecordTypes,
) -> TResult<TokenStream> {
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<Item = &'a str>,
) -> TResult<TokenStream> {
let arg_idents: Vec<syn::Ident> = args.map(utils::new_ident).collect::<Result<_, _>>()?;
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<TokenStream> {
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<Item = &'a IFunctionArg>,
records: &'r FCERecordTypes,
) -> TResult<Vec<TokenStream>> {
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::<TResult<Vec<_>>>()
}
fn generate_output_type(output_types: &[IType], records: &FCERecordTypes) -> TResult<TokenStream> {
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<Option<&IType>> {
match output_types.len() {
0 => Ok(None),
1 => Ok(Some(&output_types[0])),
_ => Err(TestGeneratorError::ManyFnOutputsUnsupported),
}
}

View File

@ -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<Vec<TokenStream>> {
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::<TResult<Vec<_>>>()
}
fn prepare_field<'f>(
fields: impl ExactSizeIterator<Item = &'f IRecordFieldType>,
records: &FCERecordTypes,
) -> TResult<Vec<TokenStream>> {
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::<TResult<Vec<_>>>()
}

View File

@ -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<syn::Ident> {
let extended_module_name = format!("__fce_generated_{}", module_name);
new_ident(&extended_module_name)
}
pub(super) fn generate_record_name(record_name: &str) -> TResult<syn::Ident> {
let extended_record_name = format!("{}", record_name);
new_ident(&extended_record_name)
}
pub(super) fn generate_struct_name(struct_name: &str) -> TResult<syn::Ident> {
let extended_struct_name = format!("FCEGeneratedStruct{}", struct_name);
new_ident(&extended_struct_name)
}
pub(super) fn new_ident(ident_str: &str) -> TResult<syn::Ident> {
syn::parse_str::<syn::Ident>(ident_str).map_err(Into::into)
}
pub(super) fn itype_to_tokens(itype: &IType, records: &FCERecordTypes) -> TResult<TokenStream> {
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)
}

View File

@ -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<T> = std::result::Result<T, TestGeneratorError>;

View File

@ -0,0 +1,24 @@
[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
[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'] }

View File

@ -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)),
}
}

View File

@ -10,7 +10,7 @@ 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]
@ -18,7 +18,7 @@ path = "src/lib.rs"
crate-type = ["rlib"]
[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"

View File

@ -32,7 +32,7 @@ macro_rules! module_manifest {
+ __FCE_SDK_REPOSITORY_SIZE
+ __FCE_SDK_FIELD_PREFIX_SIZE * 4;
const fn append_data(
const fn __fce_sdk_append_data(
mut manifest: [u8; __FCE_MANIFEST_SIZE],
data: &'static str,
offset: usize,
@ -63,10 +63,10 @@ macro_rules! module_manifest {
let manifest: [u8; __FCE_MANIFEST_SIZE] = [0; __FCE_MANIFEST_SIZE];
let offset = 0;
let (manifest, offset) = append_data(manifest, $authors, offset);
let (manifest, offset) = append_data(manifest, $version, offset);
let (manifest, offset) = append_data(manifest, $description, offset);
let (manifest, _) = append_data(manifest, $repository, offset);
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
}

View File

@ -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"] }

View File

@ -28,7 +28,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 {
/// ...

View File

@ -31,7 +31,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

View File

@ -33,7 +33,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);
@ -44,7 +44,7 @@ pub(crate) struct ExternDescriptor {
/// }
/// ```
///
/// ```
/// ```ignore
/// quote! {
/// extern "C" {
/// #[link_name = "foo_link_name"]

26
fluence-test/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[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"
[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"] }

42
fluence-test/src/lib.rs Normal file
View File

@ -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;
}

29
fluence/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[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"
[dependencies]
fluence-sdk-macro = { path = "../crates/fce-macro", version = "=0.5.0" }
fluence-sdk-main = { path = "../crates/main", version = "=0.5.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"]

View File

@ -68,6 +68,8 @@
#![warn(rust_2018_idioms)]
pub use fluence_sdk_macro::fce;
#[cfg(feature = "fce-test")]
pub use fluence_sdk_test_macro::fce_test;
pub use fluence_sdk_main::CallParameters;
pub use fluence_sdk_main::SecurityTetraplet;