outbound.rs (6059B)
1 #![forbid(unsafe_code)] 2 3 use crate::errors::BaseRelayError; 4 use std::str; 5 use tangle_protocol::{RelayMessage, SubscriptionId}; 6 use tangle_store_pocket::PocketOwnedEvent; 7 8 #[derive(Debug, Clone, PartialEq)] 9 pub enum RuntimeRelayMessage { 10 Event { 11 subscription_id: SubscriptionId, 12 event: PocketOwnedEvent, 13 }, 14 Protocol(RelayMessage), 15 } 16 17 impl RuntimeRelayMessage { 18 pub(crate) fn event(subscription_id: SubscriptionId, event: PocketOwnedEvent) -> Self { 19 Self::Event { 20 subscription_id, 21 event, 22 } 23 } 24 25 pub fn encode(&self) -> Result<String, BaseRelayError> { 26 match self { 27 Self::Event { 28 subscription_id, 29 event, 30 } => encode_pocket_event_message(subscription_id, event), 31 Self::Protocol(message) => Ok(message.encode()), 32 } 33 } 34 35 pub(crate) fn map_protocol(self, mapper: impl FnOnce(RelayMessage) -> RelayMessage) -> Self { 36 match self { 37 Self::Event { 38 subscription_id, 39 event, 40 } => Self::Event { 41 subscription_id, 42 event, 43 }, 44 Self::Protocol(message) => Self::Protocol(mapper(message)), 45 } 46 } 47 48 pub(crate) fn into_protocol_control_message(self) -> Result<RelayMessage, BaseRelayError> { 49 match self { 50 Self::Event { .. } => Err(BaseRelayError::error( 51 "event-bearing runtime messages must be encoded from Pocket events", 52 )), 53 Self::Protocol(message) => Ok(message), 54 } 55 } 56 } 57 58 impl From<RelayMessage> for RuntimeRelayMessage { 59 fn from(message: RelayMessage) -> Self { 60 Self::Protocol(message) 61 } 62 } 63 64 pub(crate) fn protocol_control_messages( 65 messages: Vec<RuntimeRelayMessage>, 66 ) -> Result<Vec<RelayMessage>, BaseRelayError> { 67 messages 68 .into_iter() 69 .map(RuntimeRelayMessage::into_protocol_control_message) 70 .collect() 71 } 72 73 #[cfg(test)] 74 pub(crate) fn protocol_messages_for_test( 75 messages: Vec<RuntimeRelayMessage>, 76 ) -> Result<Vec<RelayMessage>, BaseRelayError> { 77 messages 78 .into_iter() 79 .map(|message| match message { 80 RuntimeRelayMessage::Event { 81 subscription_id, 82 event, 83 } => Ok(RelayMessage::Event { 84 subscription_id, 85 event: crate::pocket_conversion::pocket_event_to_tangle(&event)?, 86 }), 87 RuntimeRelayMessage::Protocol(message) => Ok(message), 88 }) 89 .collect() 90 } 91 92 fn encode_pocket_event_message( 93 subscription_id: &SubscriptionId, 94 event: &PocketOwnedEvent, 95 ) -> Result<String, BaseRelayError> { 96 let subscription = serde_json::to_string(subscription_id.as_str()).map_err(|error| { 97 BaseRelayError::error(format!("outbound subscription encode failed: {error}")) 98 })?; 99 let event_json = event.as_json().map_err(|error| { 100 BaseRelayError::error(format!("outbound Pocket event encode failed: {error}")) 101 })?; 102 let event_json = str::from_utf8(&event_json).map_err(|error| { 103 BaseRelayError::error(format!("outbound Pocket event JSON is not UTF-8: {error}")) 104 })?; 105 Ok(format!(r#"["EVENT",{subscription},{event_json}]"#)) 106 } 107 108 #[cfg(test)] 109 mod tests { 110 use super::RuntimeRelayMessage; 111 use crate::pocket_conversion::tangle_event_to_pocket; 112 use serde_json::json; 113 use tangle_protocol::{RelayMessage, SubscriptionId, event_to_value, relay_message_to_value}; 114 use tangle_test_support::{FixtureKey, tangle_v2_event}; 115 116 #[test] 117 fn outbound_pocket_event_encoding_preserves_event_fields() { 118 let event = tangle_v2_event( 119 FixtureKey::Member, 120 1_714_124_433, 121 1, 122 vec![tangle_protocol::Tag::from_parts("t", &["market"]).expect("tag")], 123 "fresh carrots", 124 ) 125 .expect("event"); 126 let pocket = tangle_event_to_pocket(&event).expect("pocket"); 127 let subscription_id = SubscriptionId::new("outbound-event").expect("subscription"); 128 let encoded = RuntimeRelayMessage::event(subscription_id.clone(), pocket) 129 .encode() 130 .expect("encoded"); 131 132 assert_eq!( 133 serde_json::from_str::<serde_json::Value>(&encoded).expect("json"), 134 json!(["EVENT", subscription_id.as_str(), event_to_value(&event)]) 135 ); 136 } 137 138 #[test] 139 fn outbound_protocol_messages_still_use_protocol_encoder() { 140 let subscription_id = SubscriptionId::new("outbound-eose").expect("subscription"); 141 let message = RelayMessage::Eose(subscription_id); 142 143 assert_eq!( 144 serde_json::from_str::<serde_json::Value>( 145 &RuntimeRelayMessage::from(message.clone()) 146 .encode() 147 .expect("encoded") 148 ) 149 .expect("json"), 150 relay_message_to_value(&message) 151 ); 152 } 153 154 #[test] 155 fn protocol_mapping_never_rewrites_event_payloads() { 156 let subscription_id = SubscriptionId::new("outbound-map").expect("subscription"); 157 let event = tangle_v2_event( 158 FixtureKey::Member, 159 1_714_124_433, 160 1, 161 Vec::new(), 162 "unchanged", 163 ) 164 .expect("event"); 165 let pocket = tangle_event_to_pocket(&event).expect("pocket"); 166 let event_message = RuntimeRelayMessage::event(subscription_id.clone(), pocket); 167 let mapped_event = event_message 168 .clone() 169 .map_protocol(|_| RelayMessage::Notice("must not replace event payload".to_owned())); 170 assert_eq!(mapped_event, event_message); 171 172 let mapped_protocol = 173 RuntimeRelayMessage::from(RelayMessage::Notice("internal diagnostic".to_owned())) 174 .map_protocol(|_| RelayMessage::Notice("public code".to_owned())); 175 assert_eq!( 176 mapped_protocol, 177 RuntimeRelayMessage::from(RelayMessage::Notice("public code".to_owned())) 178 ); 179 } 180 }