diff --git a/Cargo.lock b/Cargo.lock index f91846b78..58b1a30a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3071,6 +3071,7 @@ dependencies = [ "num-traits", "serde", "serde_derive", + "serde_json", "solana-define-syscall", "solana-frozen-abi", "solana-frozen-abi-macro", diff --git a/instruction/Cargo.toml b/instruction/Cargo.toml index 199f03fed..4ccc84738 100644 --- a/instruction/Cargo.toml +++ b/instruction/Cargo.toml @@ -24,7 +24,7 @@ frozen-abi = [ "serde", "std", ] -serde = ["dep:serde", "dep:serde_derive", "solana-pubkey/serde"] +serde = ["dep:serde", "dep:serde_derive", "dep:serde_json", "solana-pubkey/serde"] std = [] syscalls = ["std"] @@ -34,6 +34,7 @@ borsh = { workspace = true, optional = true } num-traits = { workspace = true } serde = { workspace = true, optional = true } serde_derive = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } solana-frozen-abi = { workspace = true, optional = true } solana-frozen-abi-macro = { workspace = true, optional = true } solana-pubkey = { workspace = true, default-features = false } @@ -47,7 +48,7 @@ wasm-bindgen = { workspace = true } solana-define-syscall = { workspace = true } [dev-dependencies] -solana-instruction = { path = ".", features = ["borsh"] } +solana-instruction = { path = ".", features = ["borsh", "serde"] } [lints] workspace = true diff --git a/instruction/src/error.rs b/instruction/src/error.rs index ea213cd85..f74f9d3fe 100644 --- a/instruction/src/error.rs +++ b/instruction/src/error.rs @@ -60,6 +60,7 @@ pub const INCORRECT_AUTHORITY: u64 = to_builtin!(26); feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize) )] +#[cfg_attr(feature = "serde", serde(remote = "InstructionError"))] #[derive(Debug, PartialEq, Eq, Clone)] pub enum InstructionError { /// Deprecated! Use CustomError instead! @@ -242,6 +243,35 @@ pub enum InstructionError { // conversions must also be added } +// This is a variant on a hack proposed to get around deserializing a unit +// variant or a newtype, since there's no way for an enum variant deserializer +// to work with both a newtype and nothing: +// https://github.com/serde-rs/serde/issues/1174#issuecomment-372411280 +#[cfg(all(feature = "std", feature = "serde"))] +impl<'de> serde::de::Deserialize<'de> for InstructionError { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = serde_json::Value::deserialize(deserializer)?; + if s.as_str().is_some_and(|v| v == "BorshIoError") { + Ok(Self::BorshIoError(String::new())) + } else { + Self::deserialize(s).map_err(serde::de::Error::custom) + } + } +} + +#[cfg(all(feature = "std", feature = "serde"))] +impl serde::ser::Serialize for InstructionError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + Self::serialize(self, serializer) + } +} + #[cfg(feature = "std")] impl std::error::Error for InstructionError {} @@ -462,3 +492,42 @@ impl From for InstructionError { } } } + +#[cfg(test)] +#[cfg(feature = "serde")] +mod tests { + use {super::InstructionError, std::string::ToString}; + + #[test] + fn deserialize() { + serde_json::from_str::(r#""InvalidError2""#).unwrap_err(); + serde_json::from_str::(r#"{"InvalidError2": null}"#).unwrap_err(); + serde_json::from_str::(r#"{}"#).unwrap_err(); + serde_json::from_str::(r#""Custom""#).unwrap_err(); + + assert_eq!( + InstructionError::BorshIoError("".to_string()), + serde_json::from_str::(r#""BorshIoError""#).unwrap() + ); + assert_eq!( + InstructionError::BorshIoError("42".to_string()), + serde_json::from_str::(r#"{"BorshIoError": "42"}"#).unwrap() + ); + assert_eq!( + InstructionError::InvalidError, + serde_json::from_str::(r#"{"InvalidError": null}"#).unwrap() + ); + } + + #[test] + fn serialize() { + assert_eq!( + serde_json::to_string(&InstructionError::BorshIoError("42".to_string())).unwrap(), + r#"{"BorshIoError":"42"}"# + ); + assert_eq!( + serde_json::to_string(&InstructionError::InvalidError).unwrap(), + r#""InvalidError""# + ); + } +}