Skip to content

Commit

Permalink
refactor: Introduce ForcResult and ForcError to propagate return …
Browse files Browse the repository at this point in the history
…code with error message (FuelLabs#4455)

## Description
closes FuelLabs#4420.

This PR introduces `ForcResult` and `ForcError` which enables us to
return with custom exit code via propagating the desired exit code
alongside the error message. Using `ForcResult` we can exit with
different return code for different cases. Cargo uses similar approach
for similar purposes.
  • Loading branch information
kayagokalp authored Apr 17, 2023
1 parent afc1f00 commit 5e7ad6e
Show file tree
Hide file tree
Showing 19 changed files with 141 additions and 64 deletions.
71 changes: 70 additions & 1 deletion forc-util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, E = ForcError> = Result<T, E>;

#[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<anyhow::Error> 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<anyhow::Error> for ForcError {
fn from(value: anyhow::Error) -> Self {
Self {
error: value,
exit_code: DEFAULT_ERROR_EXIT_CODE,
}
}
}

impl From<std::io::Error> 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)]
Expand Down
7 changes: 4 additions & 3 deletions forc/src/cli/commands/addr2line.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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))?;

Expand Down Expand Up @@ -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())
}
}

Expand Down
4 changes: 2 additions & 2 deletions forc/src/cli/commands/build.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand All @@ -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(())
}
6 changes: 3 additions & 3 deletions forc/src/cli/commands/check.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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(())
}
7 changes: 4 additions & 3 deletions forc/src/cli/commands/clean.rs
Original file line number Diff line number Diff line change
@@ -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. `<project-name>/out`.
#[derive(Debug, Parser)]
Expand All @@ -10,6 +10,7 @@ pub struct Command {
pub path: Option<String>,
}

pub fn exec(command: Command) -> Result<()> {
forc_clean::clean(command)
pub fn exec(command: Command) -> ForcResult<()> {
forc_clean::clean(command)?;
Ok(())
}
4 changes: 2 additions & 2 deletions forc/src/cli/commands/completions.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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(())
Expand Down
7 changes: 3 additions & 4 deletions forc/src/cli/commands/contract_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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())
}
4 changes: 2 additions & 2 deletions forc/src/cli/commands/init.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -28,7 +28,7 @@ pub struct Command {
pub name: Option<String>,
}

pub(crate) fn exec(command: Command) -> Result<()> {
pub(crate) fn exec(command: Command) -> ForcResult<()> {
forc_init::init(command)?;
Ok(())
}
11 changes: 6 additions & 5 deletions forc/src/cli/commands/new.rs
Original file line number Diff line number Diff line change
@@ -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 `<path>`.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(),
Expand All @@ -80,5 +80,6 @@ pub(crate) fn exec(command: Command) -> Result<()> {
name,
};

init(init_cmd)
init(init_cmd)?;
Ok(())
}
5 changes: 3 additions & 2 deletions forc/src/cli/commands/parse_bytecode.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions forc/src/cli/commands/plugins.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -67,7 +68,7 @@ fn format_print_description(
path: PathBuf,
print_full_path: bool,
describe: bool,
) -> Result<String> {
) -> ForcResult<String> {
let display = if print_full_path {
path.display().to_string()
} else {
Expand All @@ -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<String> {
fn print_plugin(path: PathBuf, print_full_path: bool, describe: bool) -> ForcResult<String> {
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())
}
6 changes: 3 additions & 3 deletions forc/src/cli/commands/predicate_root.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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())
}
4 changes: 2 additions & 2 deletions forc/src/cli/commands/template.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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(())
}
11 changes: 5 additions & 6 deletions forc/src/cli/commands/test.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 5e7ad6e

Please sign in to comment.