notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

message.rs (8978B)


      1 use crate::{Error, Result};
      2 use ewebsock::{WsEvent, WsMessage};
      3 
      4 #[derive(Debug, Eq, PartialEq)]
      5 pub struct CommandResult<'a> {
      6     event_id: &'a str,
      7     status: bool,
      8     message: &'a str,
      9 }
     10 
     11 #[derive(Debug, Eq, PartialEq)]
     12 pub enum RelayMessage<'a> {
     13     OK(CommandResult<'a>),
     14     Eose(&'a str),
     15     Event(&'a str, &'a str),
     16     Notice(&'a str),
     17 }
     18 
     19 #[derive(Debug)]
     20 pub enum RelayEvent<'a> {
     21     Opened,
     22     Closed,
     23     Other(&'a WsMessage),
     24     Error(Error),
     25     Message(RelayMessage<'a>),
     26 }
     27 
     28 impl<'a> From<&'a WsEvent> for RelayEvent<'a> {
     29     fn from(event: &'a WsEvent) -> RelayEvent<'a> {
     30         match event {
     31             WsEvent::Opened => RelayEvent::Opened,
     32             WsEvent::Closed => RelayEvent::Closed,
     33             WsEvent::Message(ref ws_msg) => ws_msg.into(),
     34             WsEvent::Error(s) => RelayEvent::Error(Error::Generic(s.to_owned())),
     35         }
     36     }
     37 }
     38 
     39 impl<'a> From<&'a WsMessage> for RelayEvent<'a> {
     40     fn from(wsmsg: &'a WsMessage) -> RelayEvent<'a> {
     41         match wsmsg {
     42             WsMessage::Text(s) => match RelayMessage::from_json(s).map(RelayEvent::Message) {
     43                 Ok(msg) => msg,
     44                 Err(err) => RelayEvent::Error(err),
     45             },
     46             wsmsg => RelayEvent::Other(wsmsg),
     47         }
     48     }
     49 }
     50 
     51 impl<'a> RelayMessage<'a> {
     52     pub fn eose(subid: &'a str) -> Self {
     53         RelayMessage::Eose(subid)
     54     }
     55 
     56     pub fn notice(msg: &'a str) -> Self {
     57         RelayMessage::Notice(msg)
     58     }
     59 
     60     pub fn ok(event_id: &'a str, status: bool, message: &'a str) -> Self {
     61         RelayMessage::OK(CommandResult {
     62             event_id,
     63             status,
     64             message,
     65         })
     66     }
     67 
     68     pub fn event(ev: &'a str, sub_id: &'a str) -> Self {
     69         RelayMessage::Event(sub_id, ev)
     70     }
     71 
     72     pub fn from_json(msg: &'a str) -> Result<RelayMessage<'a>> {
     73         if msg.is_empty() {
     74             return Err(Error::Empty);
     75         }
     76 
     77         // Notice
     78         // Relay response format: ["NOTICE", <message>]
     79         if &msg[0..=9] == "[\"NOTICE\"," {
     80             // TODO: there could be more than one space, whatever
     81             let start = if msg.as_bytes().get(10).copied() == Some(b' ') {
     82                 12
     83             } else {
     84                 11
     85             };
     86             let end = msg.len() - 2;
     87             return Ok(Self::notice(&msg[start..end]));
     88         }
     89 
     90         // Event
     91         // Relay response format: ["EVENT", <subscription id>, <event JSON>]
     92         if &msg[0..=7] == "[\"EVENT\"" {
     93             let mut start = 9;
     94             while let Some(&b' ') = msg.as_bytes().get(start) {
     95                 start += 1; // Move past optional spaces
     96             }
     97             if let Some(comma_index) = msg[start..].find(',') {
     98                 let subid_end = start + comma_index;
     99                 let subid = &msg[start..subid_end].trim().trim_matches('"');
    100                 return Ok(Self::event(msg, subid));
    101             } else {
    102                 return Ok(Self::event(msg, "fixme"));
    103             }
    104         }
    105 
    106         // EOSE (NIP-15)
    107         // Relay response format: ["EOSE", <subscription_id>]
    108         if &msg[0..=7] == "[\"EOSE\"," {
    109             let start = if msg.as_bytes().get(8).copied() == Some(b' ') {
    110                 10
    111             } else {
    112                 9
    113             };
    114             let end = msg.len() - 2;
    115             return Ok(Self::eose(&msg[start..end]));
    116         }
    117 
    118         // OK (NIP-20)
    119         // Relay response format: ["OK",<event_id>, <true|false>, <message>]
    120         if &msg[0..=5] == "[\"OK\"," {
    121             // TODO: fix this
    122             let event_id = &msg[7..71];
    123             let booly = &msg[73..77];
    124             let status: bool = if booly == "true" {
    125                 true
    126             } else if booly == "false" {
    127                 false
    128             } else {
    129                 return Err(Error::DecodeFailed);
    130             };
    131 
    132             return Ok(Self::ok(event_id, status, "fixme"));
    133         }
    134 
    135         Err(Error::DecodeFailed)
    136     }
    137 }
    138 
    139 #[cfg(test)]
    140 mod tests {
    141     use super::*;
    142 
    143     #[test]
    144     fn test_handle_valid_notice() -> Result<()> {
    145         let valid_notice_msg = r#"["NOTICE","Invalid event format!"]"#;
    146         let handled_valid_notice_msg = RelayMessage::notice("Invalid event format!".to_string());
    147 
    148         assert_eq!(
    149             RelayMessage::from_json(valid_notice_msg)?,
    150             handled_valid_notice_msg
    151         );
    152 
    153         Ok(())
    154     }
    155     #[test]
    156     fn test_handle_invalid_notice() {
    157         //Missing content
    158         let invalid_notice_msg = r#"["NOTICE"]"#;
    159         //The content is not string
    160         let invalid_notice_msg_content = r#"["NOTICE": 404]"#;
    161 
    162         assert_eq!(
    163             RelayMessage::from_json(invalid_notice_msg).unwrap_err(),
    164             Error::DecodeFailed
    165         );
    166         assert_eq!(
    167             RelayMessage::from_json(invalid_notice_msg_content).unwrap_err(),
    168             Error::DecodeFailed
    169         );
    170     }
    171 
    172     #[test]
    173     fn test_handle_valid_event() -> Result<()> {
    174         use tracing::debug;
    175 
    176         env_logger::init();
    177         let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#;
    178 
    179         let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5";
    180         let pubkey = "379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe";
    181         let created_at = 1612809991;
    182         let kind = 1;
    183         let tags = vec![];
    184         let content = "test";
    185         let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502";
    186 
    187         let handled_event = Event::new_dummy(id, pubkey, created_at, kind, tags, content, sig);
    188         debug!("event {:?}", handled_event);
    189 
    190         let msg = RelayMessage::from_json(valid_event_msg);
    191         debug!("msg {:?}", msg);
    192 
    193         assert_eq!(
    194             msg?,
    195             RelayMessage::event(handled_event?, "random_string".to_string())
    196         );
    197 
    198         Ok(())
    199     }
    200 
    201     #[test]
    202     fn test_handle_invalid_event() {
    203         //Mising Event field
    204         let invalid_event_msg = r#"["EVENT","random_string"]"#;
    205         //Event JSON with incomplete content
    206         let invalid_event_msg_content = r#"["EVENT","random_string",{"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#;
    207 
    208         assert_eq!(
    209             RelayMessage::from_json(invalid_event_msg).unwrap_err(),
    210             Error::DecodeFailed
    211         );
    212 
    213         assert_eq!(
    214             RelayMessage::from_json(invalid_event_msg_content).unwrap_err(),
    215             Error::DecodeFailed
    216         );
    217     }
    218 
    219     #[test]
    220     fn test_handle_valid_eose() -> Result<()> {
    221         let valid_eose_msg = r#"["EOSE","random-subscription-id"]"#;
    222         let handled_valid_eose_msg = RelayMessage::eose("random-subscription-id".to_string());
    223 
    224         assert_eq!(
    225             RelayMessage::from_json(valid_eose_msg)?,
    226             handled_valid_eose_msg
    227         );
    228 
    229         Ok(())
    230     }
    231     #[test]
    232     fn test_handle_invalid_eose() {
    233         // Missing subscription ID
    234         assert_eq!(
    235             RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(),
    236             Error::DecodeFailed
    237         );
    238 
    239         // The subscription ID is not string
    240         assert_eq!(
    241             RelayMessage::from_json(r#"["EOSE",404]"#).unwrap_err(),
    242             Error::DecodeFailed
    243         );
    244     }
    245 
    246     #[test]
    247     fn test_handle_valid_ok() -> Result<()> {
    248         let valid_ok_msg = r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#;
    249         let handled_valid_ok_msg = RelayMessage::ok(
    250             "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30".to_string(),
    251             true,
    252             "pow: difficulty 25>=24".into(),
    253         );
    254 
    255         assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg);
    256 
    257         Ok(())
    258     }
    259     #[test]
    260     fn test_handle_invalid_ok() {
    261         // Missing params
    262         assert_eq!(
    263             RelayMessage::from_json(
    264                 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#
    265             )
    266             .unwrap_err(),
    267             Error::DecodeFailed
    268         );
    269 
    270         // Invalid status
    271         assert_eq!(
    272             RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#).unwrap_err(),
    273             Error::DecodeFailed
    274         );
    275 
    276         // Invalid message
    277         assert_eq!(
    278             RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,404]"#).unwrap_err(),
    279             Error::DecodeFailed
    280         );
    281     }
    282 }