#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
const COMPAT_ERROR: &str = "state mutability cannot be both `payable` and `constant`";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum StateMutability {
Pure,
View,
#[default]
NonPayable,
Payable,
}
impl core::str::FromStr for StateMutability {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s).ok_or(())
}
}
impl StateMutability {
pub fn parse(s: &str) -> Option<Self> {
match s {
"pure" => Some(Self::Pure),
"view" => Some(Self::View),
"payable" => Some(Self::Payable),
_ => None,
}
}
#[inline]
pub const fn as_str(self) -> Option<&'static str> {
if let Self::NonPayable = self { None } else { Some(self.as_json_str()) }
}
#[inline]
pub const fn as_json_str(self) -> &'static str {
match self {
Self::Pure => "pure",
Self::View => "view",
Self::NonPayable => "nonpayable",
Self::Payable => "payable",
}
}
}
#[cfg(feature = "serde")]
pub mod serde_state_mutability_compat {
use super::*;
use serde::ser::SerializeStruct;
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<StateMutability, D::Error> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct StateMutabilityCompat {
#[serde(default)]
state_mutability: Option<StateMutability>,
#[serde(default)]
payable: Option<bool>,
#[serde(default)]
constant: Option<bool>,
}
impl StateMutabilityCompat {
fn flatten(self) -> Option<StateMutability> {
let Self { state_mutability, payable, constant } = self;
if state_mutability.is_some() {
return state_mutability;
}
match (payable.unwrap_or(false), constant.unwrap_or(false)) {
(false, false) => Some(StateMutability::default()),
(true, false) => Some(StateMutability::Payable),
(false, true) => Some(StateMutability::View),
(true, true) => None,
}
}
}
StateMutabilityCompat::deserialize(deserializer).and_then(|compat| {
compat.flatten().ok_or_else(|| serde::de::Error::custom(COMPAT_ERROR))
})
}
pub fn serialize<S: serde::Serializer>(
state_mutability: &StateMutability,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut s = serializer.serialize_struct("StateMutability", 1)?;
s.serialize_field("stateMutability", state_mutability)?;
s.end()
}
}
#[cfg(all(test, feature = "serde"))]
mod tests {
use super::*;
use alloc::string::ToString;
#[derive(Debug, Serialize, Deserialize)]
struct CompatTest {
#[serde(default, flatten, with = "serde_state_mutability_compat")]
sm: StateMutability,
}
#[test]
fn test_compat() {
let test = |expect: StateMutability, json: &str| {
let compat = serde_json::from_str::<CompatTest>(json).expect(json);
assert_eq!(compat.sm, expect, "{json:?}");
let re_ser = serde_json::to_string(&compat).expect(json);
let expect = format!(r#"{{"stateMutability":"{}"}}"#, expect.as_json_str());
assert_eq!(re_ser, expect, "{json:?}");
};
test(StateMutability::Pure, r#"{"stateMutability":"pure"}"#);
test(
StateMutability::Pure,
r#"{"stateMutability":"pure","constant":false,"payable":false}"#,
);
test(StateMutability::View, r#"{"constant":true}"#);
test(StateMutability::View, r#"{"constant":true,"payable":false}"#);
test(StateMutability::Payable, r#"{"payable":true}"#);
test(StateMutability::Payable, r#"{"constant":false,"payable":true}"#);
test(StateMutability::NonPayable, r#"{}"#);
test(StateMutability::NonPayable, r#"{"constant":false}"#);
test(StateMutability::NonPayable, r#"{"payable":false}"#);
test(StateMutability::NonPayable, r#"{"constant":false,"payable":false}"#);
let json = r#"{"constant":true,"payable":true}"#;
let e = serde_json::from_str::<CompatTest>(json).unwrap_err().to_string();
assert!(e.contains(COMPAT_ERROR), "{e:?}");
}
}