diff --git a/forc-util/src/lib.rs b/forc-util/src/lib.rs index b2b3057c7d6..4b721b69588 100644 --- a/forc-util/src/lib.rs +++ b/forc-util/src/lib.rs @@ -9,19 +9,88 @@ use anyhow::{bail, Result}; use clap::Args; use forc_tracing::{println_red_err, println_yellow_err}; use serde::{Deserialize, Serialize}; -use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::str; +use std::{ffi::OsStr, process::Termination}; use sway_core::fuel_prelude::fuel_tx; use sway_core::language::parsed::TreeType; use sway_error::error::CompileError; use sway_error::warning::CompileWarning; use sway_types::{LineCol, Spanned}; use sway_utils::constants; +use tracing::error; pub mod restricted; pub const DEFAULT_OUTPUT_DIRECTORY: &str = "out"; +pub const DEFAULT_ERROR_EXIT_CODE: u8 = 1; + +pub type ForcResult = Result; + +#[derive(Debug)] +pub struct ForcError { + error: anyhow::Error, + exit_code: u8, +} + +impl ForcError { + pub fn new(error: anyhow::Error, exit_code: u8) -> Self { + Self { error, exit_code } + } +} + +impl AsRef for ForcError { + fn as_ref(&self) -> &anyhow::Error { + &self.error + } +} + +impl From<&str> for ForcError { + fn from(value: &str) -> Self { + Self { + error: anyhow::anyhow!("{value}"), + exit_code: DEFAULT_ERROR_EXIT_CODE, + } + } +} + +impl From for ForcError { + fn from(value: anyhow::Error) -> Self { + Self { + error: value, + exit_code: DEFAULT_ERROR_EXIT_CODE, + } + } +} + +impl From for ForcError { + fn from(value: std::io::Error) -> Self { + Self { + error: value.into(), + exit_code: DEFAULT_ERROR_EXIT_CODE, + } + } +} + +impl Termination for ForcError { + fn report(self) -> std::process::ExitCode { + error!("Error: {:?}", self.error); + std::process::ExitCode::from(self.exit_code) + } +} + +#[macro_export] +macro_rules! forc_result_bail { + ($msg:literal $(,)?) => { + return $crate::ForcResult::Err(anyhow::anyhow!($msg).into()) + }; + ($err:expr $(,)?) => { + return $crate::ForcResult::Err(anyhow::anyhow!($err).into()) + }; + ($fmt:expr, $($arg:tt)*) => { + return $crate::ForcResult::Err(anyhow::anyhow!($fmt, $($arg)*).into()) + }; +} /// Added salt used to derive the contract ID. #[derive(Debug, Args, Default, Deserialize, Serialize)] diff --git a/forc/src/cli/commands/addr2line.rs b/forc/src/cli/commands/addr2line.rs index 6a7004a002b..e3ebf022b35 100644 --- a/forc/src/cli/commands/addr2line.rs +++ b/forc/src/cli/commands/addr2line.rs @@ -1,5 +1,6 @@ -use anyhow::{anyhow, Result}; +use anyhow::anyhow; use clap::Parser; +use forc_util::ForcResult; use std::collections::VecDeque; use std::fs::{self, File}; use std::io::{self, prelude::*, BufReader}; @@ -30,7 +31,7 @@ pub(crate) struct Command { pub opcode_index: usize, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { let contents = fs::read(&command.sourcemap_path) .map_err(|err| anyhow!("{:?}: could not read: {:?}", command.sourcemap_path, err))?; @@ -74,7 +75,7 @@ pub(crate) fn exec(command: Command) -> Result<()> { Ok(()) } else { - Err(anyhow!("Address did not map to any source code location")) + Err("Address did not map to any source code location".into()) } } diff --git a/forc/src/cli/commands/build.rs b/forc/src/cli/commands/build.rs index 6b418b694d7..ba52e6811d9 100644 --- a/forc/src/cli/commands/build.rs +++ b/forc/src/cli/commands/build.rs @@ -1,6 +1,6 @@ use crate::{cli, ops::forc_build}; -use anyhow::Result; use clap::Parser; +use forc_util::ForcResult; /// Compile the current or target project. /// @@ -25,7 +25,7 @@ pub struct Command { pub tests: bool, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { forc_build::build(command)?; Ok(()) } diff --git a/forc/src/cli/commands/check.rs b/forc/src/cli/commands/check.rs index 8b13f9c89e4..6ec7349d498 100644 --- a/forc/src/cli/commands/check.rs +++ b/forc/src/cli/commands/check.rs @@ -1,6 +1,6 @@ use crate::ops::forc_check; -use anyhow::Result; use clap::Parser; +use forc_util::{forc_result_bail, ForcResult}; use sway_core::{decl_engine::DeclEngine, BuildTarget, Engines, TypeEngine}; /// Check the current or target project and all of its dependencies for errors. @@ -31,13 +31,13 @@ pub struct Command { pub disable_tests: bool, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { let type_engine = TypeEngine::default(); let decl_engine = DeclEngine::default(); let engines = Engines::new(&type_engine, &decl_engine); let res = forc_check::check(command, engines)?; if !res.is_ok() { - anyhow::bail!("unable to type check"); + forc_result_bail!("unable to type check"); } Ok(()) } diff --git a/forc/src/cli/commands/clean.rs b/forc/src/cli/commands/clean.rs index 5490eeee4e3..5a00b0c4ee0 100644 --- a/forc/src/cli/commands/clean.rs +++ b/forc/src/cli/commands/clean.rs @@ -1,6 +1,6 @@ use crate::ops::forc_clean; -use anyhow::Result; use clap::Parser; +use forc_util::ForcResult; /// Removes the default forc compiler output artifact directory, i.e. `/out`. #[derive(Debug, Parser)] @@ -10,6 +10,7 @@ pub struct Command { pub path: Option, } -pub fn exec(command: Command) -> Result<()> { - forc_clean::clean(command) +pub fn exec(command: Command) -> ForcResult<()> { + forc_clean::clean(command)?; + Ok(()) } diff --git a/forc/src/cli/commands/completions.rs b/forc/src/cli/commands/completions.rs index ee8b51f9058..a52d3cf5b16 100644 --- a/forc/src/cli/commands/completions.rs +++ b/forc/src/cli/commands/completions.rs @@ -1,7 +1,7 @@ -use anyhow::Result; use clap::Command as ClapCommand; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Generator, Shell}; +use forc_util::ForcResult; /// Generate tab-completion scripts for your shell #[derive(Debug, Parser)] @@ -15,7 +15,7 @@ pub struct Command { shell: Shell, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { let mut cmd = super::super::Opt::command(); print_completions(command.shell, &mut cmd); Ok(()) diff --git a/forc/src/cli/commands/contract_id.rs b/forc/src/cli/commands/contract_id.rs index 121be0ac07a..b8a1d98e2aa 100644 --- a/forc/src/cli/commands/contract_id.rs +++ b/forc/src/cli/commands/contract_id.rs @@ -2,9 +2,8 @@ use crate::{ cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print}, ops::forc_contract_id, }; -use anyhow::Result; use clap::Parser; -use forc_util::Salt; +use forc_util::{ForcResult, Salt}; /// Determine contract-id for a contract. For workspaces outputs all contract ids in the workspace. #[derive(Debug, Parser)] @@ -23,6 +22,6 @@ pub struct Command { pub salt: Salt, } -pub(crate) fn exec(cmd: Command) -> Result<()> { - forc_contract_id::contract_id(cmd) +pub(crate) fn exec(cmd: Command) -> ForcResult<()> { + forc_contract_id::contract_id(cmd).map_err(|e| e.into()) } diff --git a/forc/src/cli/commands/init.rs b/forc/src/cli/commands/init.rs index 00fdfaf8081..86d0e767410 100644 --- a/forc/src/cli/commands/init.rs +++ b/forc/src/cli/commands/init.rs @@ -1,6 +1,6 @@ use crate::ops::forc_init; -use anyhow::Result; use clap::Parser; +use forc_util::ForcResult; /// Create a new Forc project in an existing directory. #[derive(Debug, Parser)] @@ -28,7 +28,7 @@ pub struct Command { pub name: Option, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { forc_init::init(command)?; Ok(()) } diff --git a/forc/src/cli/commands/new.rs b/forc/src/cli/commands/new.rs index a5abdfbb5c1..5c382a55b5f 100644 --- a/forc/src/cli/commands/new.rs +++ b/forc/src/cli/commands/new.rs @@ -1,7 +1,7 @@ use crate::{cli::init::Command as InitCommand, ops::forc_init::init}; -use anyhow::{anyhow, bail, Result}; +use anyhow::anyhow; use clap::Parser; -use forc_util::validate_name; +use forc_util::{forc_result_bail, validate_name, ForcResult}; use std::path::{Path, PathBuf}; /// Create a new Forc project at ``. @@ -30,7 +30,7 @@ pub struct Command { pub path: String, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { // `forc new` is roughly short-hand for `forc init`, but we create the directory first if it // doesn't yet exist. Here we create the directory if it doesn't exist then re-use the existing // `forc init` logic. @@ -60,7 +60,7 @@ pub(crate) fn exec(command: Command) -> Result<()> { let dir_path = Path::new(&path); if dir_path.exists() { - bail!( + forc_result_bail!( "Directory \"{}\" already exists.\nIf you wish to initialise a forc project inside \ this directory, consider using `forc init --path {}`", dir_path.canonicalize()?.display(), @@ -80,5 +80,6 @@ pub(crate) fn exec(command: Command) -> Result<()> { name, }; - init(init_cmd) + init(init_cmd)?; + Ok(()) } diff --git a/forc/src/cli/commands/parse_bytecode.rs b/forc/src/cli/commands/parse_bytecode.rs index bc6972f7cd7..94d8bc96371 100644 --- a/forc/src/cli/commands/parse_bytecode.rs +++ b/forc/src/cli/commands/parse_bytecode.rs @@ -1,5 +1,6 @@ -use anyhow::{anyhow, Result}; +use anyhow::anyhow; use clap::Parser; +use forc_util::ForcResult; use std::fs::{self, File}; use std::io::Read; use term_table::row::Row; @@ -12,7 +13,7 @@ pub(crate) struct Command { file_path: String, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { let mut f = File::open(&command.file_path) .map_err(|_| anyhow!("{}: file not found", command.file_path))?; let metadata = fs::metadata(&command.file_path) diff --git a/forc/src/cli/commands/plugins.rs b/forc/src/cli/commands/plugins.rs index c9d238b829d..0343020856d 100644 --- a/forc/src/cli/commands/plugins.rs +++ b/forc/src/cli/commands/plugins.rs @@ -1,6 +1,7 @@ use crate::cli::PluginsCommand; -use anyhow::{anyhow, Result}; +use anyhow::anyhow; use clap::Parser; +use forc_util::ForcResult; use std::path::{Path, PathBuf}; use tracing::info; @@ -17,7 +18,7 @@ pub struct Command { describe: bool, } -pub(crate) fn exec(command: PluginsCommand) -> Result<()> { +pub(crate) fn exec(command: PluginsCommand) -> ForcResult<()> { let PluginsCommand { print_full_path, describe, @@ -67,7 +68,7 @@ fn format_print_description( path: PathBuf, print_full_path: bool, describe: bool, -) -> Result { +) -> ForcResult { let display = if print_full_path { path.display().to_string() } else { @@ -93,7 +94,7 @@ fn format_print_description( /// paths yielded from plugin::find_all(), as well as that the file names are in valid /// unicode format since file names should be prefixed with `forc-`. Should one of these 2 /// assumptions fail, this function panics. -fn print_plugin(path: PathBuf, print_full_path: bool, describe: bool) -> Result { +fn print_plugin(path: PathBuf, print_full_path: bool, describe: bool) -> ForcResult { format_print_description(path, print_full_path, describe) - .map_err(|e| anyhow!("Could not get plugin info: {}", e)) + .map_err(|e| anyhow!("Could not get plugin info: {}", e.as_ref()).into()) } diff --git a/forc/src/cli/commands/predicate_root.rs b/forc/src/cli/commands/predicate_root.rs index 41a0f785d8a..cc81b88614c 100644 --- a/forc/src/cli/commands/predicate_root.rs +++ b/forc/src/cli/commands/predicate_root.rs @@ -1,5 +1,5 @@ -use anyhow::Result; use clap::Parser; +use forc_util::ForcResult; pub use crate::cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print}; use crate::ops::forc_predicate_root; @@ -20,6 +20,6 @@ pub struct Command { pub build_profile: BuildProfile, } -pub(crate) fn exec(cmd: Command) -> Result<()> { - forc_predicate_root::predicate_root(cmd) +pub(crate) fn exec(cmd: Command) -> ForcResult<()> { + forc_predicate_root::predicate_root(cmd).map_err(|e| e.into()) } diff --git a/forc/src/cli/commands/template.rs b/forc/src/cli/commands/template.rs index 12f07a39f2c..01d30a5d994 100644 --- a/forc/src/cli/commands/template.rs +++ b/forc/src/cli/commands/template.rs @@ -1,6 +1,6 @@ use crate::ops::forc_template; -use anyhow::Result; use clap::Parser; +use forc_util::ForcResult; /// Create a new Forc project from a git template. #[derive(Debug, Parser)] @@ -17,7 +17,7 @@ pub struct Command { pub project_name: String, } -pub(crate) fn exec(command: Command) -> Result<()> { +pub(crate) fn exec(command: Command) -> ForcResult<()> { forc_template::init(command)?; Ok(()) } diff --git a/forc/src/cli/commands/test.rs b/forc/src/cli/commands/test.rs index 37c6821923a..e5d17c1055e 100644 --- a/forc/src/cli/commands/test.rs +++ b/forc/src/cli/commands/test.rs @@ -1,10 +1,9 @@ use crate::cli; use ansi_term::Colour; -use anyhow::{bail, Result}; use clap::Parser; use forc_pkg as pkg; use forc_test::{TestRunnerCount, TestedPackage}; -use forc_util::format_log_receipts; +use forc_util::{forc_result_bail, format_log_receipts, ForcResult}; use tracing::info; /// Run the Sway unit tests for the current project. @@ -49,9 +48,9 @@ pub struct TestPrintOpts { pub print_logs: bool, } -pub(crate) fn exec(cmd: Command) -> Result<()> { +pub(crate) fn exec(cmd: Command) -> ForcResult<()> { if let Some(ref _filter) = cmd.filter { - bail!("unit test filter not yet supported"); + forc_result_bail!("unit test filter not yet supported"); } let test_runner_count = match cmd.test_threads { @@ -87,11 +86,11 @@ pub(crate) fn exec(cmd: Command) -> Result<()> { if all_tests_passed { Ok(()) } else { - bail!("Some tests failed") + Err("Some tests failed.".into()) } } -fn print_tested_pkg(pkg: &TestedPackage, test_print_opts: &TestPrintOpts) -> Result<()> { +fn print_tested_pkg(pkg: &TestedPackage, test_print_opts: &TestPrintOpts) -> ForcResult<()> { let succeeded = pkg.tests.iter().filter(|t| t.passed()).count(); let failed = pkg.tests.len() - succeeded; let mut failed_tests = Vec::new(); diff --git a/forc/src/cli/commands/update.rs b/forc/src/cli/commands/update.rs index c5460a86cb5..8a378902444 100644 --- a/forc/src/cli/commands/update.rs +++ b/forc/src/cli/commands/update.rs @@ -1,6 +1,6 @@ use crate::ops::forc_update; -use anyhow::{bail, Result}; use clap::Parser; +use forc_util::ForcResult; /// Update dependencies in the Forc dependencies directory. #[derive(Debug, Parser)] @@ -21,9 +21,11 @@ pub struct Command { pub check: bool, } -pub(crate) async fn exec(command: Command) -> Result<()> { +pub(crate) async fn exec(command: Command) -> ForcResult<()> { match forc_update::update(command).await { Ok(_) => Ok(()), - Err(e) => bail!("couldn't update dependencies: {}", e), + Err(e) => Err(format!("couldn't update dependencies: {}", e) + .as_str() + .into()), } } diff --git a/forc/src/cli/mod.rs b/forc/src/cli/mod.rs index ae97e68286a..a304e97faf7 100644 --- a/forc/src/cli/mod.rs +++ b/forc/src/cli/mod.rs @@ -5,7 +5,7 @@ use self::commands::{ predicate_root, template, test, update, }; use addr2line::Command as Addr2LineCommand; -use anyhow::{anyhow, Result}; +use anyhow::anyhow; pub use build::Command as BuildCommand; pub use check::Command as CheckCommand; use clap::{Parser, Subcommand}; @@ -13,6 +13,7 @@ pub use clean::Command as CleanCommand; pub use completions::Command as CompletionsCommand; pub(crate) use contract_id::Command as ContractIdCommand; use forc_tracing::{init_tracing_subscriber, TracingSubscriberOptions}; +use forc_util::ForcResult; pub use init::Command as InitCommand; pub use new::Command as NewCommand; use parse_bytecode::Command as ParseBytecodeCommand; @@ -78,7 +79,7 @@ enum Forc { Plugin(Vec), } -pub async fn run_cli() -> Result<()> { +pub async fn run_cli() -> ForcResult<()> { let opt = Opt::parse(); let tracing_options = TracingSubscriberOptions { verbosity: Some(opt.verbose), diff --git a/forc/src/main.rs b/forc/src/main.rs index c2a8da3bc98..96692ed570e 100644 --- a/forc/src/main.rs +++ b/forc/src/main.rs @@ -1,9 +1,6 @@ -use tracing::error; +use forc_util::ForcResult; #[tokio::main] -async fn main() { - if let Err(err) = forc::cli::run_cli().await { - error!("Error: {:?}", err); - std::process::exit(1); - } +async fn main() -> ForcResult<()> { + forc::cli::run_cli().await } diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index cc482a948fd..e2d54f5aec2 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -1,8 +1,8 @@ use crate::cli::BuildCommand; -use anyhow::Result; use forc_pkg as pkg; +use forc_util::ForcResult; -pub fn build(cmd: BuildCommand) -> Result { +pub fn build(cmd: BuildCommand) -> ForcResult { let opts = opts_from_cmd(cmd); let built = pkg::build_with_options(opts)?; Ok(built) diff --git a/forc/src/ops/forc_init.rs b/forc/src/ops/forc_init.rs index 336e3fbbd38..cc1ee604a1c 100644 --- a/forc/src/ops/forc_init.rs +++ b/forc/src/ops/forc_init.rs @@ -1,7 +1,7 @@ use crate::cli::InitCommand; use crate::utils::{defaults, program_type::ProgramType}; -use anyhow::{Context, Result}; -use forc_util::validate_name; +use anyhow::Context; +use forc_util::{forc_result_bail, validate_name, ForcResult}; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; @@ -41,7 +41,7 @@ fn print_welcome_message() { ); } -pub fn init(command: InitCommand) -> Result<()> { +pub fn init(command: InitCommand) -> ForcResult<()> { let project_dir = match &command.path { Some(p) => PathBuf::from(p), None => { @@ -50,11 +50,14 @@ pub fn init(command: InitCommand) -> Result<()> { }; if !project_dir.is_dir() { - anyhow::bail!("'{}' is not a valid directory.", project_dir.display()); + forc_result_bail!(format!( + "'{}' is not a valid directory.", + project_dir.display() + ),); } if project_dir.join(constants::MANIFEST_FILE_NAME).exists() { - anyhow::bail!( + forc_result_bail!( "'{}' already includes a Forc.toml file.", project_dir.display() ); @@ -88,10 +91,12 @@ pub fn init(command: InitCommand) -> Result<()> { (false, false, true, false, false) => InitType::Package(ProgramType::Predicate), (false, false, false, true, false) => InitType::Package(ProgramType::Library), (false, false, false, false, true) => InitType::Workspace, - _ => anyhow::bail!( - "Multiple types detected, please specify only one initialization type: \ + _ => { + forc_result_bail!( + "Multiple types detected, please specify only one initialization type: \ \n Possible Types:\n - contract\n - script\n - predicate\n - library\n - workspace" - ), + ) + } }; // Make a new directory for the project