Skip to content

Commit

Permalink
feat!: improve error on malformed abi (#1347)
Browse files Browse the repository at this point in the history
closes: #1343

BREAKING CHANGE: 
- `AbigenTarget` now holds the parsed `FullProgramAbi` and the path.
- fields of `AbigenTarget` and `Abi` are `pub(crate)`
- `Source` is removed in favor of `Abi`
  • Loading branch information
hal3e authored May 4, 2024
1 parent 52e25a6 commit a0cf378
Show file tree
Hide file tree
Showing 20 changed files with 212 additions and 201 deletions.
2 changes: 1 addition & 1 deletion packages/fuels-code-gen/src/program_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ mod generated_code;
mod resolved_type;
mod utils;

pub use abigen::{Abigen, AbigenTarget, ProgramType};
pub use abigen::{Abi, Abigen, AbigenTarget, ProgramType};
29 changes: 8 additions & 21 deletions packages/fuels-code-gen/src/program_bindings/abigen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{collections::HashSet, path::PathBuf};

pub use abigen_target::{AbigenTarget, ProgramType};
pub use abigen_target::{Abi, AbigenTarget, ProgramType};
use fuel_abi_types::abi::full_program::FullTypeDeclaration;
use inflector::Inflector;
use itertools::Itertools;
Expand All @@ -11,8 +11,7 @@ use regex::Regex;
use crate::{
error::Result,
program_bindings::{
abigen::{abigen_target::ParsedAbigenTarget, bindings::generate_bindings},
custom_types::generate_types,
abigen::bindings::generate_bindings, custom_types::generate_types,
generated_code::GeneratedCode,
},
utils::ident,
Expand All @@ -35,9 +34,7 @@ impl Abigen {
/// for, and of what nature (Contract, Script or Predicate).
/// * `no_std`: don't use the Rust std library.
pub fn generate(targets: Vec<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
let parsed_targets = Self::parse_targets(targets)?;

let generated_code = Self::generate_code(no_std, parsed_targets)?;
let generated_code = Self::generate_code(no_std, targets)?;

let use_statements = generated_code.use_statements_for_uniquely_named_types();

Expand Down Expand Up @@ -68,10 +65,7 @@ impl Abigen {
.expect("Wasm hotfix failed!")
}

fn generate_code(
no_std: bool,
parsed_targets: Vec<ParsedAbigenTarget>,
) -> Result<GeneratedCode> {
fn generate_code(no_std: bool, parsed_targets: Vec<AbigenTarget>) -> Result<GeneratedCode> {
let custom_types = Self::filter_custom_types(&parsed_targets);
let shared_types = Self::filter_shared_types(custom_types);

Expand All @@ -83,11 +77,11 @@ impl Abigen {
}

fn generate_all_bindings(
parsed_targets: Vec<ParsedAbigenTarget>,
targets: Vec<AbigenTarget>,
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
parsed_targets
targets
.into_iter()
.map(|target| Self::generate_binding(target, no_std, shared_types))
.fold_ok(GeneratedCode::default(), |acc, generated_code| {
Expand All @@ -96,7 +90,7 @@ impl Abigen {
}

fn generate_binding(
target: ParsedAbigenTarget,
target: AbigenTarget,
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
Expand Down Expand Up @@ -128,13 +122,6 @@ impl Abigen {
GeneratedCode::new(code, Default::default(), no_std)
}

fn parse_targets(targets: Vec<AbigenTarget>) -> Result<Vec<ParsedAbigenTarget>> {
targets
.into_iter()
.map(|target| target.try_into())
.collect()
}

fn generate_shared_types(
shared_types: HashSet<FullTypeDeclaration>,
no_std: bool,
Expand All @@ -150,7 +137,7 @@ impl Abigen {
}

fn filter_custom_types(
all_types: &[ParsedAbigenTarget],
all_types: &[AbigenTarget],
) -> impl Iterator<Item = &FullTypeDeclaration> {
all_types
.iter()
Expand Down
126 changes: 98 additions & 28 deletions packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,121 @@
use std::{convert::TryFrom, path::PathBuf, str::FromStr};
use std::{
convert::TryFrom,
env, fs,
path::{Path, PathBuf},
str::FromStr,
};

use fuel_abi_types::abi::full_program::FullProgramABI;
use proc_macro2::Ident;

use crate::{
error,
error::{Error, Result},
utils::Source,
};
use crate::error::{error, Error, Result};

#[derive(Debug, Clone)]
pub struct AbigenTarget {
pub name: String,
pub abi: String,
pub program_type: ProgramType,
pub(crate) name: String,
pub(crate) source: Abi,
pub(crate) program_type: ProgramType,
}

impl AbigenTarget {
pub fn new(name: String, source: Abi, program_type: ProgramType) -> Self {
Self {
name,
source,
program_type,
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn source(&self) -> &Abi {
&self.source
}

pub fn program_type(&self) -> ProgramType {
self.program_type
}
}

pub(crate) struct Abi {
#[derive(Debug, Clone)]
pub struct Abi {
pub(crate) path: Option<PathBuf>,
pub(crate) abi: FullProgramABI,
}

pub(crate) struct ParsedAbigenTarget {
pub name: String,
pub source: Abi,
pub program_type: ProgramType,
}
impl Abi {
pub fn load_from(path: impl AsRef<Path>) -> Result<Abi> {
let path = Self::canonicalize_path(path.as_ref())?;

impl TryFrom<AbigenTarget> for ParsedAbigenTarget {
type Error = Error;
let json_abi = fs::read_to_string(&path).map_err(|e| {
error!(
"failed to read `abi` file with path {}: {}",
path.display(),
e
)
})?;
let abi = Self::parse_from_json(&json_abi)?;

fn try_from(value: AbigenTarget) -> Result<Self> {
Ok(Self {
name: value.name,
source: parse_program_abi(&value.abi)?,
program_type: value.program_type,
Ok(Abi {
path: Some(path),
abi,
})
}

fn canonicalize_path(path: &Path) -> Result<PathBuf> {
let current_dir = env::current_dir()
.map_err(|e| error!("unable to get current directory: ").combine(e))?;

let root = current_dir.canonicalize().map_err(|e| {
error!(
"unable to canonicalize current directory {}: ",
current_dir.display()
)
.combine(e)
})?;

let path = root.join(path);

if path.is_relative() {
path.canonicalize().map_err(|e| {
error!(
"unable to canonicalize file from working dir {} with path {}: {}",
env::current_dir()
.map(|cwd| cwd.display().to_string())
.unwrap_or_else(|err| format!("??? ({err})")),
path.display(),
e
)
})
} else {
Ok(path)
}
}

fn parse_from_json(json_abi: &str) -> Result<FullProgramABI> {
FullProgramABI::from_json_abi(json_abi)
.map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e))
}

pub fn path(&self) -> Option<&PathBuf> {
self.path.as_ref()
}

pub fn abi(&self) -> &FullProgramABI {
&self.abi
}
}

fn parse_program_abi(abi_source: &str) -> Result<Abi> {
let source = Source::parse(abi_source).expect("failed to parse JSON ABI");
impl FromStr for Abi {
type Err = Error;

fn from_str(json_abi: &str) -> Result<Self> {
let abi = Abi::parse_from_json(json_abi)?;

let json_abi_str = source.get().expect("failed to parse JSON ABI from string");
let abi = FullProgramABI::from_json_abi(&json_abi_str)?;
let path = source.path();
Ok(Abi { path, abi })
Ok(Abi { path: None, abi })
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
error::Result,
program_bindings::{
abigen::{
abigen_target::ParsedAbigenTarget,
abigen_target::AbigenTarget,
bindings::{
contract::contract_bindings, predicate::predicate_bindings, script::script_bindings,
},
Expand All @@ -19,7 +19,7 @@ mod predicate;
mod script;
mod utils;

pub(crate) fn generate_bindings(target: ParsedAbigenTarget, no_std: bool) -> Result<GeneratedCode> {
pub(crate) fn generate_bindings(target: AbigenTarget, no_std: bool) -> Result<GeneratedCode> {
let bindings_generator = match target.program_type {
ProgramType::Script => script_bindings,
ProgramType::Contract => contract_bindings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub(crate) fn extract_main_fn(abi: &[FullABIFunction]) -> Result<&FullABIFunctio
.map(|candidate| candidate.name())
.collect::<Vec<_>>();
Err(error!(
"ABI must have one and only one function with the name 'main'. Got: {fn_names:?}"
"`abi` must have only one function with the name 'main'. Got: {fn_names:?}"
))
}
}
Expand Down Expand Up @@ -45,7 +45,7 @@ mod tests {

assert_eq!(
err.to_string(),
r#"ABI must have one and only one function with the name 'main'. Got: ["main", "another", "main"]"#
r#"`abi` must have only one function with the name 'main'. Got: ["main", "another", "main"]"#
);
}

Expand Down
3 changes: 0 additions & 3 deletions packages/fuels-code-gen/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
pub use fuel_abi_types::utils::{ident, safe_ident, TypePath};
pub use source::Source;

mod source;
Loading

0 comments on commit a0cf378

Please sign in to comment.