use context_deserialize::ContextDeserialize; use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::value::Value; use types::ForkName; /// The metadata of type M should be set to `EmptyMetadata` if you don't care about adding fields other than /// version. If you *do* care about adding other fields you can mix in any type that implements /// `Deserialize`. #[derive(Debug, PartialEq, Clone, Serialize)] pub struct ForkVersionedResponse { pub version: ForkName, #[serde(flatten)] pub metadata: M, pub data: T, } // Used for responses to V1 endpoints that don't have a version field. /// The metadata of type M should be set to `EmptyMetadata` if you don't care about adding fields other than /// version. If you *do* care about adding other fields you can mix in any type that implements /// `Deserialize`. #[derive(Debug, PartialEq, Clone, Serialize)] pub struct UnversionedResponse { #[serde(flatten)] pub metadata: M, pub data: T, } #[derive(Debug, PartialEq, Clone, Serialize)] #[serde(untagged)] pub enum BeaconResponse { ForkVersioned(ForkVersionedResponse), Unversioned(UnversionedResponse), } impl BeaconResponse { pub fn version(&self) -> Option { match self { BeaconResponse::ForkVersioned(response) => Some(response.version), BeaconResponse::Unversioned(_) => None, } } pub fn data(&self) -> &T { match self { BeaconResponse::ForkVersioned(response) => &response.data, BeaconResponse::Unversioned(response) => &response.data, } } pub fn metadata(&self) -> &M { match self { BeaconResponse::ForkVersioned(response) => &response.metadata, BeaconResponse::Unversioned(response) => &response.metadata, } } } /// Metadata type similar to unit (i.e. `()`) but deserializes from a map (`serde_json::Value`). /// /// Unfortunately the braces are semantically significant, i.e. `struct EmptyMetadata;` does not /// work. #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] pub struct EmptyMetadata {} /// Fork versioned response with extra information about finalization & optimistic execution. pub type ExecutionOptimisticFinalizedBeaconResponse = BeaconResponse; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ExecutionOptimisticFinalizedMetadata { pub execution_optimistic: Option, pub finalized: Option, } impl<'de, T, M> Deserialize<'de> for ForkVersionedResponse where T: ContextDeserialize<'de, ForkName>, M: DeserializeOwned, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Helper { version: ForkName, #[serde(flatten)] metadata: Value, data: Value, } let helper = Helper::deserialize(deserializer)?; // Deserialize metadata let metadata = serde_json::from_value(helper.metadata).map_err(serde::de::Error::custom)?; // Deserialize `data` using ContextDeserialize let data = T::context_deserialize(helper.data, helper.version) .map_err(serde::de::Error::custom)?; Ok(ForkVersionedResponse { version: helper.version, metadata, data, }) } } impl<'de, T, M> Deserialize<'de> for UnversionedResponse where T: DeserializeOwned, M: DeserializeOwned, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Helper { #[serde(flatten)] metadata: M, data: T, } let helper = Helper::deserialize(deserializer)?; Ok(UnversionedResponse { metadata: helper.metadata, data: helper.data, }) } } impl BeaconResponse { pub fn map_data(self, f: impl FnOnce(T) -> U) -> BeaconResponse { match self { BeaconResponse::ForkVersioned(response) => { BeaconResponse::ForkVersioned(response.map_data(f)) } BeaconResponse::Unversioned(response) => { BeaconResponse::Unversioned(response.map_data(f)) } } } pub fn into_data(self) -> T { match self { BeaconResponse::ForkVersioned(response) => response.data, BeaconResponse::Unversioned(response) => response.data, } } } impl UnversionedResponse { pub fn map_data(self, f: impl FnOnce(T) -> U) -> UnversionedResponse { let UnversionedResponse { metadata, data } = self; UnversionedResponse { metadata, data: f(data), } } } impl ForkVersionedResponse { /// Apply a function to the inner `data`, potentially changing its type. pub fn map_data(self, f: impl FnOnce(T) -> U) -> ForkVersionedResponse { let ForkVersionedResponse { version, metadata, data, } = self; ForkVersionedResponse { version, metadata, data: f(data), } } } impl From> for BeaconResponse { fn from(response: ForkVersionedResponse) -> Self { BeaconResponse::ForkVersioned(response) } } impl From> for BeaconResponse { fn from(response: UnversionedResponse) -> Self { BeaconResponse::Unversioned(response) } } #[cfg(test)] mod fork_version_response_tests { use crate::beacon_response::ExecutionOptimisticFinalizedMetadata; use crate::{ ExecutionPayload, ExecutionPayloadBellatrix, ForkName, ForkVersionedResponse, MainnetEthSpec, UnversionedResponse, }; use serde_json::json; #[test] fn fork_versioned_response_deserialize_correct_fork() { type E = MainnetEthSpec; let response_json = serde_json::to_string(&json!(ForkVersionedResponse::> { version: ForkName::Bellatrix, metadata: Default::default(), data: ExecutionPayload::Bellatrix(ExecutionPayloadBellatrix::default()), })) .unwrap(); let result: Result>, _> = serde_json::from_str(&response_json); assert!(result.is_ok()); } #[test] fn fork_versioned_response_deserialize_incorrect_fork() { type E = MainnetEthSpec; let response_json = serde_json::to_string(&json!(ForkVersionedResponse::> { version: ForkName::Capella, metadata: Default::default(), data: ExecutionPayload::Bellatrix(ExecutionPayloadBellatrix::default()), })) .unwrap(); let result: Result>, _> = serde_json::from_str(&response_json); assert!(result.is_err()); } // The following test should only pass by having the attribute #[serde(flatten)] on the metadata #[test] fn unversioned_response_serialize_dezerialize_round_trip_test() { // Create an UnversionedResponse with some data let data = UnversionedResponse { metadata: ExecutionOptimisticFinalizedMetadata { execution_optimistic: Some(false), finalized: Some(false), }, data: "some_test_data".to_string(), }; let serialized = serde_json::to_string(&data); let deserialized = serde_json::from_str(&serialized.unwrap()).expect("Failed to deserialize"); assert_eq!(data, deserialized); } }