use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(transparent)]
pub struct Abi {
pub items: Vec<AbiItem>,
}
impl Abi {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn from_items(items: Vec<AbiItem>) -> Self {
Self { items }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "type")]
pub enum AbiItem {
#[serde(rename = "function")]
Function(Function),
#[serde(rename = "constructor")]
Constructor(Constructor),
#[serde(rename = "receive")]
Receive(Receive),
#[serde(rename = "fallback")]
Fallback(Fallback),
#[serde(rename = "event")]
Event(Event),
#[serde(rename = "error")]
Error(Error),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub name: String,
pub inputs: Vec<Param>,
pub outputs: Vec<Param>,
#[serde(rename = "stateMutability")]
pub state_mutability: StateMutability,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
pub inputs: Vec<Param>,
#[serde(rename = "stateMutability")]
pub state_mutability: StateMutability,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Receive {
#[serde(rename = "stateMutability")]
pub state_mutability: StateMutability,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Fallback {
#[serde(rename = "stateMutability")]
pub state_mutability: StateMutability,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Event {
pub name: String,
pub inputs: Vec<EventParam>,
pub anonymous: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Error {
pub name: String,
pub inputs: Vec<Param>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub components: Option<Vec<Component>>,
#[serde(rename = "internalType", default)]
pub internal_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct EventParam {
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub components: Option<Vec<Component>>,
pub indexed: bool,
#[serde(rename = "internalType", default)]
pub internal_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Component {
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub components: Option<Vec<Component>>,
#[serde(rename = "internalType", default)]
pub internal_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum StateMutability {
Pure,
View,
Nonpayable,
Payable,
}
#[cfg(test)]
mod tests {
use std::fs;
use super::*;
use rayon::prelude::*;
use serde::de::IntoDeserializer;
use serde_json::Value;
use serde_path_to_error::deserialize;
use walkdir::WalkDir;
fn find_deserialization_error(content: &str) -> String {
let value: Value = serde_json::from_str(content).expect("Failed to parse JSON");
find_error_in_value(&value, "root")
}
fn find_error_in_value(value: &Value, json_path: &str) -> String {
if let Some(obj) = value.as_object() {
for (key, val) in obj {
let result = find_error_in_value(val, &format!("{}.{}", json_path, key));
if !result.is_empty() {
return result;
}
}
if let Some(type_str) = obj.get("type").and_then(|v| v.as_str()) {
return try_parse_abi_item(value, json_path, type_str);
}
}
if let Some(arr) = value.as_array() {
for (i, item) in arr.iter().enumerate() {
let result = find_error_in_value(item, &format!("{}[{}]", json_path, i));
if !result.is_empty() {
return result;
}
}
}
String::new()
}
fn try_parse_abi_item(value: &Value, json_path: &str, item_type: &str) -> String {
let json_str = serde_json::to_string_pretty(value)
.unwrap_or_else(|_| String::from("Could not serialize value"));
macro_rules! try_parse {
($type:ty) => {
match deserialize::<_, $type>(value.clone().into_deserializer()) {
Ok(_) => String::new(),
Err(err) => {
let field_path = err.path().to_string();
format!(
"Failed to parse {} at path '{}':\nField: '{}'\nError: {}\nJSON:\n{}",
item_type, json_path, field_path, err, json_str
)
}
}
};
}
match item_type {
"function" => try_parse!(Function),
"constructor" => try_parse!(Constructor),
"receive" => try_parse!(Receive),
"fallback" => try_parse!(Fallback),
"event" => try_parse!(Event),
"error" => try_parse!(Error),
_ => String::new(),
}
}
#[test]
fn fixtures() {
let entries: Vec<walkdir::DirEntry> = WalkDir::new("fixtures/abi")
.into_iter()
.filter_map(Result::ok)
.filter(|entry| entry.file_type().is_file())
.filter(|entry| entry.path().extension().map_or(false, |e| e == "json"))
.collect();
entries.par_iter().for_each(|entry| {
let content = fs::read_to_string(entry.path()).expect("Failed to read fixture file");
let result: Result<Abi, serde_json::Error> = serde_json::from_str(&content);
if let Err(_) = result {
let error_msg = find_deserialization_error(&content);
panic!("Failed to parse {:?}: {}", entry.path(), error_msg);
}
});
}
}