Skip to content

Commit

Permalink
feat(json-abi): allow serde_json::from_{value,reader} (#684)
Browse files Browse the repository at this point in the history
* feat(json-abi): allow `serde_json::from_{value,reader}`

* chore: clippy
  • Loading branch information
DaniPopes authored and klkvr committed Aug 4, 2024
1 parent 9bce1ff commit ba8bf82
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 81 deletions.
91 changes: 48 additions & 43 deletions crates/json-abi/src/internal_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,54 +39,31 @@ pub enum InternalType {
},
}

impl From<BorrowedInternalType<'_>> for InternalType {
#[inline]
fn from(borrowed: BorrowedInternalType<'_>) -> Self {
match borrowed {
BorrowedInternalType::AddressPayable(s) => Self::AddressPayable(s.to_string()),
BorrowedInternalType::Contract(s) => Self::Contract(s.to_string()),
BorrowedInternalType::Enum { contract, ty } => {
Self::Enum { contract: contract.map(String::from), ty: ty.to_string() }
}
BorrowedInternalType::Struct { contract, ty } => {
Self::Struct { contract: contract.map(String::from), ty: ty.to_string() }
}
BorrowedInternalType::Other { contract, ty } => {
Self::Other { contract: contract.map(String::from), ty: ty.to_string() }
}
}
}
}

impl fmt::Display for InternalType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_borrowed().fmt(f)
}
}

impl Serialize for InternalType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_borrowed().serialize(serializer)
}
}

impl<'de> Deserialize<'de> for InternalType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(ItVisitor).map(Into::into)
#[inline]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(ItVisitor)
}
}

impl InternalType {
/// Parse a string into an instance, taking ownership of data
#[inline]
pub fn parse(s: &str) -> Option<Self> {
BorrowedInternalType::parse(s).map(Into::into)
BorrowedInternalType::parse(s).map(BorrowedInternalType::into_owned)
}

/// True if the instance is a `struct` variant.
Expand Down Expand Up @@ -248,7 +225,7 @@ impl Serialize for BorrowedInternalType<'_> {
impl<'de: 'a, 'a> Deserialize<'de> for BorrowedInternalType<'a> {
#[inline]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(ItVisitor)
deserializer.deserialize_str(BorrowedItVisitor)
}
}

Expand Down Expand Up @@ -278,33 +255,61 @@ impl<'a> BorrowedInternalType<'a> {
Some(Self::Other { contract: None, ty: v })
}
}

pub(crate) fn into_owned(self) -> InternalType {
match self {
Self::AddressPayable(s) => InternalType::AddressPayable(s.to_string()),
Self::Contract(s) => InternalType::Contract(s.to_string()),
Self::Enum { contract, ty } => {
InternalType::Enum { contract: contract.map(String::from), ty: ty.to_string() }
}
Self::Struct { contract, ty } => {
InternalType::Struct { contract: contract.map(String::from), ty: ty.to_string() }
}
Self::Other { contract, ty } => {
InternalType::Other { contract: contract.map(String::from), ty: ty.to_string() }
}
}
}
}

const VISITOR_EXPECTED: &str = "a valid internal type";

pub(crate) struct ItVisitor;

impl<'de> Visitor<'de> for ItVisitor {
type Value = InternalType;

fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(VISITOR_EXPECTED)
}

fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
BorrowedInternalType::parse(v)
.map(BorrowedInternalType::into_owned)
.ok_or_else(|| E::invalid_value(serde::de::Unexpected::Str(v), &VISITOR_EXPECTED))
}
}

const BORROWED_VISITOR_EXPECTED: &str = "a valid borrowed internal type";

pub(crate) struct BorrowedItVisitor;

impl<'de> Visitor<'de> for BorrowedItVisitor {
type Value = BorrowedInternalType<'de>;

fn expecting(&self, formatter: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
write!(formatter, "a valid internal type")
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(BORROWED_VISITOR_EXPECTED)
}

fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
BorrowedInternalType::parse(v).ok_or_else(|| {
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid internal type")
E::invalid_value(serde::de::Unexpected::Str(v), &BORROWED_VISITOR_EXPECTED)
})
}

fn visit_str<E>(self, _v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
// `from_reader` copies the bytes into a Vec before calling this
// method. Because the lifetime is unspecified, we can't borrow from it.
// As a result, we don't support `from_reader`.
Err(serde::de::Error::custom(
"Using serde_json::from_reader is not supported. Instead, buffer the reader contents into a string, as in alloy_json_abi::JsonAbi::load.",
))
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
Err(E::invalid_value(serde::de::Unexpected::Str(v), &BORROWED_VISITOR_EXPECTED))
}
}

Expand Down
102 changes: 70 additions & 32 deletions crates/json-abi/src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ use crate::{
utils::{mk_eparam, mk_param, validate_identifier},
InternalType,
};
use alloc::{
borrow::{Cow, ToOwned},
string::String,
vec::Vec,
};
use alloc::{borrow::Cow, string::String, vec::Vec};
use core::{fmt, str::FromStr};
use parser::{Error, ParameterSpecifier, TypeSpecifier};
use serde::{de::Unexpected, Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -58,14 +54,14 @@ impl fmt::Display for Param {

impl<'de> Deserialize<'de> for Param {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
BorrowedParam::deserialize(deserializer).and_then(|inner| {
ParamInner::deserialize(deserializer).and_then(|inner| {
if inner.indexed.is_none() {
inner.validate_fields()?;
Ok(Self {
name: inner.name.to_owned(),
ty: inner.ty.to_owned(),
internal_type: inner.internal_type.map(Into::into),
components: inner.components.into_owned(),
name: inner.name,
ty: inner.ty,
internal_type: inner.internal_type,
components: inner.components,
})
} else {
Err(serde::de::Error::custom("indexed is not supported in params"))
Expand Down Expand Up @@ -271,8 +267,8 @@ impl Param {
}

#[inline]
fn as_inner(&self) -> BorrowedParam<'_> {
BorrowedParam {
fn as_inner(&self) -> BorrowedParamInner<'_> {
BorrowedParamInner {
name: &self.name,
ty: &self.ty,
indexed: None,
Expand Down Expand Up @@ -350,14 +346,14 @@ impl fmt::Display for EventParam {

impl<'de> Deserialize<'de> for EventParam {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
BorrowedParam::deserialize(deserializer).and_then(|inner| {
ParamInner::deserialize(deserializer).and_then(|inner| {
inner.validate_fields()?;
Ok(Self {
name: inner.name.to_owned(),
ty: inner.ty.to_owned(),
name: inner.name,
ty: inner.ty,
indexed: inner.indexed.unwrap_or(false),
internal_type: inner.internal_type.map(Into::into),
components: inner.components.into_owned(),
components: inner.components,
})
})
}
Expand Down Expand Up @@ -564,8 +560,8 @@ impl EventParam {
}

#[inline]
fn as_inner(&self) -> BorrowedParam<'_> {
BorrowedParam {
fn as_inner(&self) -> BorrowedParamInner<'_> {
BorrowedParamInner {
name: &self.name,
ty: &self.ty,
indexed: Some(self.indexed),
Expand All @@ -575,8 +571,47 @@ impl EventParam {
}
}

#[derive(Deserialize)]
struct ParamInner {
#[serde(default)]
name: String,
#[serde(rename = "type")]
ty: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
indexed: Option<bool>,
#[serde(rename = "internalType", default, skip_serializing_if = "Option::is_none")]
internal_type: Option<InternalType>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
components: Vec<Param>,
}

impl Serialize for ParamInner {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_borrowed().serialize(serializer)
}
}

impl ParamInner {
#[inline]
fn validate_fields<E: serde::de::Error>(&self) -> Result<(), E> {
self.as_borrowed().validate_fields()
}

#[inline]
fn as_borrowed(&self) -> BorrowedParamInner<'_> {
BorrowedParamInner {
name: &self.name,
ty: &self.ty,
indexed: self.indexed,
internal_type: self.internal_type.as_ref().map(InternalType::as_borrowed),
components: Cow::Borrowed(&self.components),
}
}
}

#[derive(Serialize, Deserialize)]
struct BorrowedParam<'a> {
struct BorrowedParamInner<'a> {
#[serde(default)]
name: &'a str,
#[serde(rename = "type")]
Expand All @@ -589,8 +624,7 @@ struct BorrowedParam<'a> {
components: Cow<'a, [Param]>,
}

impl BorrowedParam<'_> {
#[inline(always)]
impl BorrowedParamInner<'_> {
fn validate_fields<E: serde::de::Error>(&self) -> Result<(), E> {
validate_identifier!(self.name);

Expand Down Expand Up @@ -623,22 +657,26 @@ mod tests {
use super::*;

#[test]
fn param_from_str() {
fn param_from_json() {
let param = r#"{
"internalType": "string",
"name": "reason",
"type": "string"
}"#;
let param = serde_json::from_str::<Param>(param).unwrap();
assert_eq!(
param,
Param {
name: "reason".into(),
ty: "string".into(),
internal_type: Some(InternalType::Other { contract: None, ty: "string".into() }),
components: vec![],
}
);
let expected = Param {
name: "reason".into(),
ty: "string".into(),
internal_type: Some(InternalType::Other { contract: None, ty: "string".into() }),
components: vec![],
};

assert_eq!(serde_json::from_str::<Param>(param).unwrap(), expected);

let param_value = serde_json::from_str::<serde_json::Value>(param).unwrap();
assert_eq!(serde_json::from_value::<Param>(param_value).unwrap(), expected);

let reader = std::io::Cursor::new(param);
assert_eq!(serde_json::from_reader::<_, Param>(reader).unwrap(), expected);
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions crates/json-abi/tests/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ fn abi_test(s: &str, path: &str, run_solc: bool) {
assert_eq!(len, abi2.len());
assert_eq!(abi1, abi2);

let abi_items2: Vec<AbiItem<'_>> = serde_json::from_reader(std::io::Cursor::new(s)).unwrap();
assert_eq!(abi_items2, abi_items);

#[cfg(all(feature = "std", feature = "serde_json"))]
load_test(path, &abi1);
to_sol_test(path, &abi1, run_solc);
Expand Down
9 changes: 3 additions & 6 deletions crates/json-abi/tests/it/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,12 @@ fn test_constructor() {

#[test]
#[cfg_attr(miri, ignore = "no fs")]
fn no_from_reader() {
fn from_reader() {
let path = "abi/Abiencoderv2Test.json";
let file_path: String = format!("tests/{path}");
let file: File = File::open(file_path).unwrap();
let buffer: BufReader<File> = BufReader::new(file);

let res = serde_json::from_reader::<_, JsonAbi>(buffer);
assert!(res.is_err());
assert!(
format!("{}", res.unwrap_err()).contains("Using serde_json::from_reader is not supported.")
);
let abi = serde_json::from_reader::<_, JsonAbi>(buffer).unwrap();
assert_eq!(abi.len(), 1);
}

0 comments on commit ba8bf82

Please sign in to comment.