tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

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 }