notedeck

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

message.rs (9172B)


      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.len() >= 12 && &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\"," && msg.len() >= 78 {
    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!");
    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!(matches!(
    163             RelayMessage::from_json(invalid_notice_msg).unwrap_err(),
    164             Error::DecodeFailed
    165         ));
    166         assert!(matches!(
    167             RelayMessage::from_json(invalid_notice_msg_content).unwrap_err(),
    168             Error::DecodeFailed
    169         ));
    170     }
    171 
    172     /*
    173     #[test]
    174     fn test_handle_valid_event() -> Result<()> {
    175         use tracing::debug;
    176 
    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 = Note::new_dummy(id, pubkey, created_at, kind, tags, content, sig).expect("ev");
    188         debug!("event {:?}", handled_event);
    189 
    190         let msg = RelayMessage::from_json(valid_event_msg).expect("valid json");
    191         debug!("msg {:?}", msg);
    192 
    193         let note_json = serde_json::to_string(&handled_event).expect("json ev");
    194 
    195         assert_eq!(
    196             msg,
    197             RelayMessage::event(&note_json, "random_string")
    198         );
    199 
    200         Ok(())
    201     }
    202 
    203     #[test]
    204     fn test_handle_invalid_event() {
    205         //Mising Event field
    206         let invalid_event_msg = r#"["EVENT","random_string"]"#;
    207         //Event JSON with incomplete content
    208         let invalid_event_msg_content = r#"["EVENT","random_string",{"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#;
    209 
    210         assert!(matches!(
    211             RelayMessage::from_json(invalid_event_msg).unwrap_err(),
    212             Error::DecodeFailed
    213         ));
    214 
    215         assert!(matches!(
    216             RelayMessage::from_json(invalid_event_msg_content).unwrap_err(),
    217             Error::DecodeFailed
    218         ));
    219     }
    220     */
    221 
    222     #[test]
    223     fn test_handle_valid_eose() -> Result<()> {
    224         let valid_eose_msg = r#"["EOSE","random-subscription-id"]"#;
    225         let handled_valid_eose_msg = RelayMessage::eose("random-subscription-id");
    226 
    227         assert_eq!(
    228             RelayMessage::from_json(valid_eose_msg)?,
    229             handled_valid_eose_msg
    230         );
    231 
    232         Ok(())
    233     }
    234 
    235     // TODO: fix these tests
    236     /*
    237     #[test]
    238     fn test_handle_invalid_eose() {
    239         // Missing subscription ID
    240         assert!(matches!(
    241             RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(),
    242             Error::DecodeFailed
    243         ));
    244 
    245         // The subscription ID is not string
    246         assert!(matches!(
    247             RelayMessage::from_json(r#"["EOSE",404]"#).unwrap_err(),
    248             Error::DecodeFailed
    249         ));
    250     }
    251 
    252     #[test]
    253     fn test_handle_valid_ok() -> Result<()> {
    254         let valid_ok_msg = r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#;
    255         let handled_valid_ok_msg = RelayMessage::ok(
    256             "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",
    257             true,
    258             "pow: difficulty 25>=24".into(),
    259         );
    260 
    261         assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg);
    262 
    263         Ok(())
    264     }
    265     */
    266 
    267     #[test]
    268     fn test_handle_invalid_ok() {
    269         // Missing params
    270         assert!(matches!(
    271             RelayMessage::from_json(
    272                 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#
    273             )
    274             .unwrap_err(),
    275             Error::DecodeFailed
    276         ));
    277 
    278         // Invalid status
    279         assert!(
    280             matches!(RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#).unwrap_err(),
    281             Error::DecodeFailed)
    282         );
    283 
    284         // Invalid message
    285         assert!(
    286             matches!(RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,404]"#).unwrap_err(),
    287             Error::DecodeFailed)
    288         );
    289     }
    290 }