Bladeren bron

Add custom content parsing by kind

Cesar Rodas 2 jaren geleden
bovenliggende
commit
4bcf917073

+ 4 - 1
crates/types/src/lib.rs

@@ -176,7 +176,10 @@ mod test {
         assert!(message.as_event_from_server().is_some());
 
         let (_, event) = message.as_event_from_server().expect("event");
-        assert_eq!("🤙".to_owned(), event.content.content);
+        assert_eq!(
+            "🤙".to_owned(),
+            event.data.content.try_to_string().expect("string")
+        );
     }
 
     #[test]

+ 58 - 0
crates/types/src/types/content/mod.rs

@@ -0,0 +1,58 @@
+use serde::ser::{self, Serializer};
+use thiserror::Error;
+
+pub mod profile;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Issue at the serializer: {0}")]
+    Json(#[from] serde_json::Error),
+}
+
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub enum Content {
+    Profile(profile::Profile),
+    Text(String),
+    RecommendServer(String),
+    Unknown(u32, String),
+}
+
+impl ser::Serialize for Content {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(
+            &self
+                .try_to_string()
+                .map_err(|e| ser::Error::custom(e.to_string()))?,
+        )
+    }
+}
+
+impl Content {
+    pub fn kind(&self) -> u32 {
+        match self {
+            Self::Profile(_) => 0,
+            Self::Text(_) => 1,
+            Self::RecommendServer(_) => 2,
+            Self::Unknown(k, _) => *k,
+        }
+    }
+
+    pub fn deserialize(kind: u32, content: &str) -> Result<Self, Error> {
+        match kind {
+            0 => Ok(Self::Profile(serde_json::from_str(content)?)),
+            1 => Ok(Self::Text(content.to_owned())),
+            2 => Ok(Self::RecommendServer(content.to_owned())),
+            _ => Ok(Self::Unknown(kind, content.to_owned())),
+        }
+    }
+
+    pub fn try_to_string(&self) -> Result<String, Error> {
+        match self {
+            Self::Profile(p) => Ok(serde_json::to_string(&p)?),
+            Self::Text(t) | Self::RecommendServer(t) | Self::Unknown(_, t) => Ok(t.to_owned()),
+        }
+    }
+}

+ 19 - 0
crates/types/src/types/content/profile.rs

@@ -0,0 +1,19 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
+pub struct Profile {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub lud06: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub website: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub nip05: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub picture: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub display_name: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub about: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+}

+ 55 - 40
crates/types/src/types/event.rs

@@ -1,7 +1,10 @@
-use super::{Id, Signature};
+use super::{content, Content, Id, Signature};
 use chrono::{DateTime, Utc};
 use secp256k1::{schnorr, Message, XOnlyPublicKey};
-use serde::{de, Deserialize, Serialize};
+use serde::{
+    de::{self, Deserializer},
+    Deserialize, Serialize,
+};
 use serde_json::json;
 use sha2::{Digest, Sha256};
 use thiserror::Error;
@@ -19,55 +22,74 @@ pub enum Error {
 
     #[error("Provided ID is not correct")]
     InvalidProvidedId,
+
+    #[error("Internal error: {0}")]
+    Internal(#[from] content::Error),
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Metadata {
+#[derive(Debug, Clone, Serialize)]
+pub struct Data {
     #[serde(rename = "pubkey")]
     pub public_key: Id,
     #[serde(with = "super::ts_seconds")]
     pub created_at: DateTime<Utc>,
     pub kind: u32,
     pub tags: Vec<super::Tag>,
-    pub content: String,
+    pub content: Content,
+}
+
+impl<'de> de::Deserialize<'de> for Data {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct MetadataInternal {
+            #[serde(rename = "pubkey")]
+            pub public_key: Id,
+            #[serde(with = "super::ts_seconds")]
+            pub created_at: DateTime<Utc>,
+            pub kind: u32,
+            pub tags: Vec<super::Tag>,
+            pub content: String,
+        }
+        let s: MetadataInternal = MetadataInternal::deserialize(deserializer)?;
+        let content = Content::deserialize(s.kind, &s.content)
+            .map_err(|d| de::Error::custom(d.to_string()))?;
+        Ok(Self {
+            public_key: s.public_key,
+            created_at: s.created_at,
+            kind: s.kind,
+            tags: s.tags,
+            content,
+        })
+    }
 }
 
-impl Metadata {
-    pub fn new<T>(
+impl Data {
+    pub fn new(
         public_key: Id,
-        kind: u32,
         tags: Vec<super::Tag>,
-        content: &T,
+        content: Content,
         created_at: Option<DateTime<Utc>>,
-    ) -> Result<Self, Error>
-    where
-        T: ?Sized + Serialize,
-    {
+    ) -> Result<Self, Error> {
         Ok(Self {
             public_key,
             created_at: created_at.unwrap_or_else(Utc::now),
-            kind,
             tags,
-            content: serde_json::to_string(&content)?,
+            kind: content.kind(),
+            content,
         })
     }
 
-    #[inline]
-    pub fn content<'a, T>(&'a self) -> Result<T, Error>
-    where
-        T: de::Deserialize<'a>,
-    {
-        Ok(serde_json::from_str(&self.content)?)
-    }
-
     pub fn id(&self) -> Result<Id, Error> {
         let data_to_hash = serde_json::to_string(&json!(vec![
             json!(0),
             json!(self.public_key.to_string()),
             json!(self.created_at.timestamp()),
-            json!(self.kind),
+            json!(self.content.kind()),
             json!(self.tags),
-            json!(self.content),
+            json!(self.content.try_to_string()?),
         ]))?;
         let mut hasher = Sha256::new();
         hasher.update(&data_to_hash);
@@ -79,37 +101,29 @@ impl Metadata {
 pub struct Event {
     pub id: Id,
     #[serde(flatten)]
-    pub content: Metadata,
+    pub data: Data,
     #[serde(rename = "sig")]
     pub signature: Signature,
 }
 
 impl Event {
-    pub fn new(content: Metadata, signature: Signature) -> Result<Self, Error> {
-        let id = content.id()?;
-        Self::verify_signature(&content.public_key, &signature, &id)?;
+    pub fn new(data: Data, signature: Signature) -> Result<Self, Error> {
+        let id = data.id()?;
+        Self::verify_signature(&data.public_key, &signature, &id)?;
 
         Ok(Self {
             id,
-            content,
+            data,
             signature,
         })
     }
 
-    #[inline]
-    pub fn content<'a, T>(&'a self) -> Result<T, Error>
-    where
-        T: de::Deserialize<'a>,
-    {
-        self.content.content()
-    }
-
     pub fn is_valid(&self) -> Result<(), Error> {
-        let calculated_id = self.content.id()?;
+        let calculated_id = self.data.id()?;
         if calculated_id != self.id {
             return Err(Error::InvalidProvidedId);
         }
-        Self::verify_signature(&self.content.public_key, &self.signature, &self.id)
+        Self::verify_signature(&self.data.public_key, &self.signature, &self.id)
     }
 
     fn verify_signature(
@@ -132,6 +146,7 @@ mod test {
         let json = "{\"content\":\"{\\\"lud06\\\":\\\"lnbc1p3a4wxvpp5x0pa6gr55fq5s9d3dxs0vz77mqxgdw63hhtgtlfz5zvm65847vnqdqqcqpjsp5402c8rtqxd4j97rnvuejuwl4sg473g6wg08d67fvn7qc4gtpkfks9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqmqz9gxqyjw5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glclleasn65surjcsqqqqlgqqqqqeqqjqyxj968tem9ps6ttm9ukv6ag4yc6qmgj2svrccfgp4n83fpktr3dsx6fq7grfzlqt982aaemahg9q29vzl9f627kh4j8h8xc2z2mtpdqqjlekah\\\",\\\"website\\\":\\\"\\\",\\\"nip05\\\":\\\"cesar@cesar.com.py\\\",\\\"picture\\\":\\\"https://pbs.twimg.com/profile_images/1175432935337537536/_Peu9vuJ_400x400.jpg\\\",\\\"display_name\\\":\\\"C\\\",\\\"about\\\":\\\"Rust and PHP\\\",\\\"name\\\":\\\"c\\\"}\",\"created_at\":1678476588,\"id\":\"3800c787a23288641c0b96cbcc87c26cbd3ea7bee53b7748422fdb100fb7b9f0\",\"kind\":0,\"pubkey\":\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\",\"sig\":\"c8a12ce96833e4cd67bce0e9e50f831262ef0f0c0cff5e56c38a0c90867ed1a6621e9692948ef5e85a7ca3726c3f0f43fa7e1992536bc457317123bca8784f5f\",\"tags\":[]}";
 
         let obj: Event = serde_json::from_str(json).expect("valid");
+        obj.is_valid().unwrap();
         assert!(obj.is_valid().is_ok());
     }
 }

+ 3 - 1
crates/types/src/types/mod.rs

@@ -1,4 +1,5 @@
 pub mod addr;
+pub mod content;
 pub mod event;
 pub mod filter;
 pub mod id;
@@ -72,7 +73,8 @@ pub(crate) mod ts_seconds {
 
 pub use self::{
     addr::Addr,
-    event::{Event, Metadata},
+    content::Content,
+    event::{Data, Event},
     filter::Filter,
     id::Id,
     signature::Signature,