diff --git a/src/close.rs b/src/close.rs new file mode 100644 index 0000000..4f79281 --- /dev/null +++ b/src/close.rs @@ -0,0 +1,61 @@ +use crate::error::{Error, Result}; +use serde::{Deserialize, Deserializer, Serialize}; + +// Container for a request to close a subscription +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(transparent)] +pub struct CloseCmd { + cmds: Vec, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Close { + id: String, +} + +impl<'de> Deserialize<'de> for Close { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut v: serde_json::Value = Deserialize::deserialize(deserializer)?; + // this shoud be an exactly 2-element array + // verify the first element is a String, CLOSE + // get the subscription from the second element. + + // check for array + let va = v + .as_array_mut() + .ok_or(serde::de::Error::custom("not array"))?; + + // check length + if va.len() == 2 { + return Err(serde::de::Error::custom("not exactly 2 fields")); + } + let mut i = va.into_iter(); + // get command ("REQ") and ensure it is a string + let req_cmd_str: serde_json::Value = i.next().unwrap().take(); + let req = req_cmd_str.as_str().ok_or(serde::de::Error::custom( + "first element of request was not a string", + ))?; + if req != "CLOSE" { + return Err(serde::de::Error::custom("missing CLOSE command")); + } + + // ensure sub id is a string + let sub_id_str: serde_json::Value = i.next().unwrap().take(); + let sub_id = sub_id_str + .as_str() + .ok_or(serde::de::Error::custom("missing subscription id"))?; + + Ok(Close { + id: sub_id.to_owned(), + }) + } +} + +impl Close { + pub fn parse(json: &str) -> Result { + serde_json::from_str(json).map_err(|e| Error::JsonParseFailed(e)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7fafd82..73594e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod close; pub mod error; pub mod event; pub mod proto; diff --git a/src/proto.rs b/src/proto.rs index 256f27f..3ba616c 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,5 +1,5 @@ use crate::error::{Error, Result}; -use crate::{event, subscription}; +use crate::{close, event, subscription}; use log::{debug, info}; use uuid::Uuid; @@ -30,7 +30,7 @@ impl Proto { // A raw message with the expected type #[derive(PartialEq, Debug)] pub enum NostrRawMessage { - Event(String), + Ev(String), Sub(String), Close(String), } @@ -40,13 +40,14 @@ pub enum NostrRawMessage { pub enum NostrRequest { Ev(event::Event), Sub(subscription::Subscription), + Close(close::Close), } // Wrap the message in the expected request type fn msg_type_wrapper(msg: String) -> Result { // check prefix. if msg.starts_with(r#"["EVENT","#) { - Ok(NostrRawMessage::Event(msg)) + Ok(NostrRawMessage::Ev(msg)) } else if msg.starts_with(r#"["REQ","#) { Ok(NostrRawMessage::Sub(msg)) } else if msg.starts_with(r#"["CLOSE","#) { @@ -60,9 +61,9 @@ pub fn parse_type(msg: String) -> Result { // turn this raw string into a parsed request let typ = msg_type_wrapper(msg)?; match typ { - NostrRawMessage::Event(_) => Err(Error::EventParseFailed), + NostrRawMessage::Ev(_) => Err(Error::EventParseFailed), NostrRawMessage::Sub(m) => Ok(NostrRequest::Sub(subscription::Subscription::parse(&m)?)), - NostrRawMessage::Close(_) => Err(Error::CloseParseFailed), + NostrRawMessage::Close(m) => Ok(NostrRequest::Close(close::Close::parse(&m)?)), } }