From 9cb2426d215b5028d11e6ad50af14bcb8a6e31a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 19 Jan 2024 11:36:35 +0100 Subject: [PATCH] Add schema generation functionality --- src/actions.rs | 2 + src/actions/build.rs | 86 +++++++++---------------------------------- src/actions/schema.rs | 49 ++++++++++++++++++++++++ src/actions/utils.rs | 60 ++++++++++++++++++++++++++++++ src/cli.rs | 15 ++++++++ src/command.rs | 7 ++++ 6 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 src/actions/schema.rs create mode 100644 src/actions/utils.rs diff --git a/src/actions.rs b/src/actions.rs index 9767cf2..b851358 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -4,5 +4,7 @@ pub mod build; pub mod clean; pub mod generate; pub mod init; +pub mod schema; pub mod test; pub mod update; +mod utils; diff --git a/src/actions/build.rs b/src/actions/build.rs index a882a58..fb05734 100644 --- a/src/actions/build.rs +++ b/src/actions/build.rs @@ -1,6 +1,7 @@ //! Module for managing and building backends. -use crate::{command, errors::Error, log, odra_toml::Contract, paths, project::Project}; +use super::utils; +use crate::{command, log, paths, project::Project}; /// BuildAction configuration. pub struct BuildAction<'a> { @@ -22,57 +23,23 @@ impl<'a> BuildAction<'a> { impl BuildAction<'_> { /// Main function that runs the whole workflow for a backend. pub fn build(&self) { - self.check_target_requirements(); - self.validate_contract_name_argument(); + utils::check_target_requirements(); + utils::validate_contract_name_argument(self.project, self.contracts_names()); self.build_wasm_files(); self.optimize_wasm_files(); } - /// Returns list of contract to process. - fn contracts(&self) -> Vec { - let names = self.parse_contracts_names(); - let odra_toml = self.project.odra_toml(); - match names.is_empty() { - true => odra_toml.contracts, - false => odra_toml - .contracts - .into_iter() - .filter(|c| names.contains(&c.struct_name())) - .collect(), - } - } - - /// Check if wasm32-unknown-unknown target is installed. - fn check_target_requirements(&self) { - if !command::command_output("rustup target list --installed") - .contains("wasm32-unknown-unknown") - { - Error::WasmTargetNotInstalled.print_and_die(); - } - } - - /// Check if contract name argument is valid if set. - fn validate_contract_name_argument(&self) { - let names = self.parse_contracts_names(); - names.iter().for_each(|contract_name| { - if !self - .project - .odra_toml() - .contracts - .iter() - .any(|c| c.struct_name() == *contract_name) - { - Error::ContractNotFound(contract_name.clone()).print_and_die(); - } - }); - } - /// Build .wasm files. fn build_wasm_files(&self) { log::info("Generating wasm files..."); command::mkdir(paths::wasm_dir(self.project.project_root())); - for contract in self.contracts() { + let contracts = + utils::contracts(self.project, self.contracts_names()).unwrap_or_else(|_| { + Error::FailedToParseArgument("contracts_names".to_string()).print_and_die() + }); + + for contract in contracts { let build_contract = format!("{}_build_contract", &contract.module_name()); command::cargo_build_wasm_files( self.project.project_root(), @@ -103,7 +70,12 @@ impl BuildAction<'_> { /// Run wasm-strip on *.wasm files in wasm directory. fn optimize_wasm_files(&self) { log::info("Optimizing wasm files..."); - for contract in self.contracts() { + let contracts = + utils::contracts(self.project, self.contracts_names()).unwrap_or_else(|_| { + Error::FailedToParseArgument("contracts_names".to_string()).print_and_die() + }); + + for contract in contracts { command::wasm_strip(&contract.struct_name(), self.project.project_root()); if self.project.is_workspace() { command::wasm_strip( @@ -114,29 +86,7 @@ impl BuildAction<'_> { } } - fn parse_contracts_names(&self) -> Vec { - match &self.contracts_names { - Some(string) => remove_extra_spaces(string) - .map(|string| { - string - .split(' ') - .map(ToString::to_string) - .collect::>() - }) - .unwrap_or_else(|_| { - Error::FailedToParseArgument("contracts_names".to_string()).print_and_die() - }), - None => vec![], - } - } -} - -fn remove_extra_spaces(input: &str) -> Result { - // Ensure there are no other separators - if input.chars().any(|c| c.is_whitespace() && c != ' ') { - return Err("Input contains non-space whitespace characters"); + fn contracts_names(&self) -> String { + self.contracts_names.clone().unwrap_or_default() } - - let trimmed = input.split_whitespace().collect::>().join(" "); - Ok(trimmed) } diff --git a/src/actions/schema.rs b/src/actions/schema.rs new file mode 100644 index 0000000..1524f1e --- /dev/null +++ b/src/actions/schema.rs @@ -0,0 +1,49 @@ +//! Module for generating contracts schema. + +use super::utils; +use crate::{command, log, project::Project}; + +/// SchemaAction configuration. +pub struct SchemaAction<'a> { + project: &'a Project, + contracts_names: Option, +} + +impl<'a> SchemaAction<'a> { + /// Crate a new SchemaAction for a given configuration. + pub fn new(project: &'a Project, contracts_names: Option) -> Self { + SchemaAction { + project, + contracts_names, + } + } +} + +impl SchemaAction<'_> { + /// Main function that runs the whole workflow. + pub fn build(&self) { + utils::check_target_requirements(); + utils::validate_contract_name_argument(self.project, self.contracts_names()); + self.generate_schema_files(); + } + + /// Generates *_schema.json files. + fn generate_schema_files(&self) { + log::info("Generating schema files..."); + let contracts = + utils::contracts(self.project, self.contracts_names()).unwrap_or_else(|_| { + Error::FailedToParseArgument("contracts_names".to_string()).print_and_die() + }); + for contract in contracts { + command::cargo_generate_schema_files( + self.project.project_root(), + &contract.struct_name(), + &contract.module_name(), + ); + } + } + + fn contracts_names(&self) -> String { + self.contracts_names.clone().unwrap_or_default() + } +} diff --git a/src/actions/utils.rs b/src/actions/utils.rs new file mode 100644 index 0000000..018d518 --- /dev/null +++ b/src/actions/utils.rs @@ -0,0 +1,60 @@ +use crate::{command, errors::Error, odra_toml::Contract, project::Project}; + +/// Check if wasm32-unknown-unknown target is installed. +pub fn check_target_requirements() { + if !command::command_output("rustup target list --installed").contains("wasm32-unknown-unknown") + { + Error::WasmTargetNotInstalled.print_and_die(); + } +} + +/// Returns list of contract to process. +pub fn contracts(project: &Project, names_string: String) -> Result, &'static str> { + let names = parse_contracts_names(names_string)?; + let odra_toml = project.odra_toml(); + Ok(match names.is_empty() { + true => odra_toml.contracts, + false => odra_toml + .contracts + .into_iter() + .filter(|c| names.contains(&c.struct_name())) + .collect(), + }) +} + +/// Check if contract name argument is valid if set. +pub fn validate_contract_name_argument(project: &Project, names_string: String) { + let names = parse_contracts_names(names_string); + names.iter().for_each(|contract_name| { + if !project + .odra_toml() + .contracts + .iter() + .any(|c| c.struct_name() == *contract_name) + { + Error::ContractNotFound(contract_name.clone()).print_and_die(); + } + }); +} + +fn remove_extra_spaces(input: &str) -> Result { + // Ensure there are no other separators + if input.chars().any(|c| c.is_whitespace() && c != ' ') { + return Err("Input contains non-space whitespace characters"); + } + + let trimmed = input.split_whitespace().collect::>().join(" "); + Ok(trimmed) +} + +fn parse_contracts_names(names_string: String) -> Result, &'static str> { + match names_string.is_empty() { + true => Ok(vec![]), + false => remove_extra_spaces(&names_string).map(|string| { + string + .split(' ') + .map(ToString::to_string) + .collect::>() + }), + } +} diff --git a/src/cli.rs b/src/cli.rs index 73162f6..f201187 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,6 +10,7 @@ use crate::{ clean::clean_action, generate::GenerateAction, init::InitAction, + schema::SchemaAction, test::TestAction, update::update_action, }, @@ -51,6 +52,8 @@ pub enum OdraSubcommand { Init(InitCommand), /// Builds the project, including backend and producing wasm files. Build(BuildCommand), + /// Generates schema for a given contract. + Schema(SchemaCommand), /// Runs test. Without the backend parameter, the tests will be run against Mock VM. Test(TestCommand), /// Generates boilerplate code for contracts. @@ -94,6 +97,14 @@ pub struct BuildCommand { pub contracts_names: Option, } +#[derive(clap::Args)] +/// `cargo odra schema` +pub struct SchemaCommand { + /// Contracts names separated by a space that matches the names in Odra.toml. + #[clap(value_parser, long, short)] + pub contracts_names: Option, +} + #[derive(clap::Args, Debug)] /// `cargo odra test` pub struct TestCommand { @@ -167,5 +178,9 @@ pub fn make_action() { OdraSubcommand::Completions { shell } => { shell.generate(&mut Cargo::command(), &mut std::io::stdout()); } + OdraSubcommand::Schema(schema) => { + let project = Project::detect(current_dir); + SchemaAction::new(&project, schema.contracts_names).build(); + } } } diff --git a/src/command.rs b/src/command.rs index 0e8975b..e3faa56 100644 --- a/src/command.rs +++ b/src/command.rs @@ -125,6 +125,13 @@ pub fn cargo_build_wasm_files(current_dir: PathBuf, contract_name: &str, module_ ); } +/// Build schema files. +pub fn cargo_generate_schema_files(current_dir: PathBuf, contract_name: &str, module_name: &str) { + env::set_var(ODRA_MODULE_ENV_KEY, contract_name); + let gen_schema = format!("{}_build_schema", module_name); + cargo(current_dir, "run", vec!["--bin", &gen_schema, "--release"]); +} + /// Update a cargo module. pub fn cargo_update(current_dir: PathBuf) { cargo(current_dir, "update", vec![]);