Jelajahi Sumber

Working on types

Cesar Rodas 2 tahun lalu
melakukan
a2d367d81c

+ 16 - 0
crates/types/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "nostr-protocol-types"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bech32 = "0.9.1"
+chrono = "0.4.23"
+hex = "0.4"
+rand = "0.8.5"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+thiserror = "1.0.39"
+

+ 3 - 0
crates/types/src/client/mod.rs

@@ -0,0 +1,3 @@
+pub mod request;
+
+pub use request::Request;

+ 139 - 0
crates/types/src/client/request.rs

@@ -0,0 +1,139 @@
+use crate::types;
+use serde::{
+    de::{self, Deserializer},
+    ser::{self, SerializeSeq, Serializer},
+    Deserialize, Serialize,
+};
+
+#[derive(Debug, Clone, Default)]
+pub struct Request {
+    pub subscription_id: types::SubscriptionId,
+    pub filters: Vec<types::Filter>,
+}
+
+impl<'de> de::Deserialize<'de> for Request {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        #[derive(Deserialize, Serialize, Debug)]
+        #[serde(untagged)]
+        enum StringOrFilter {
+            Filter(types::Filter),
+            String(String),
+        }
+
+        impl<'de> StringOrFilter {
+            pub fn to_filter<D>(&self, element: usize) -> Result<types::Filter, D::Error>
+            where
+                D: Deserializer<'de>,
+            {
+                match self {
+                    Self::Filter(f) => Ok(f.clone()),
+                    _ => Err(de::Error::custom(format!(
+                        "Expecting a filter in element {}, got a filter object",
+                        element,
+                    ))),
+                }
+            }
+
+            pub fn to_string<D>(&self, element: usize) -> Result<String, D::Error>
+            where
+                D: Deserializer<'de>,
+            {
+                match self {
+                    Self::String(string) => Ok(string.to_owned()),
+                    _ => Err(de::Error::custom(format!(
+                        "Expecting an string in element {}, got a filter object",
+                        element,
+                    ))),
+                }
+            }
+        }
+
+        let s: Vec<StringOrFilter> = Deserialize::deserialize(deserializer)?;
+        if s.len() < 2 {
+            return Err(de::Error::custom(
+                "Array too small, it must have at least 2 elements",
+            ));
+        }
+        let header = s[0].to_string::<D>(0)?;
+        let subscription_id = s[1]
+            .to_string::<D>(1)?
+            .as_str()
+            .try_into()
+            .map_err(|e: types::subscription_id::Error| de::Error::custom(e.to_string()))?;
+
+        if header != "REQ" {
+            return Err(de::Error::custom(format!(
+                "Invalid header, got {} and expected REQ",
+                header
+            )));
+        }
+
+        let mut index = 1;
+        Ok(Request {
+            subscription_id,
+            filters: if s.len() > 2 {
+                s[2..]
+                    .iter()
+                    .map(|filter| {
+                        index += 1;
+                        filter.to_filter::<D>(index)
+                    })
+                    .collect::<Result<Vec<types::Filter>, D::Error>>()?
+            } else {
+                vec![]
+            },
+        })
+    }
+}
+
+impl ser::Serialize for Request {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut seq = serializer.serialize_seq(Some(self.filters.len() + 2))?;
+        seq.serialize_element("REQ")?;
+        seq.serialize_element(&self.subscription_id)?;
+        for element in self.filters.iter() {
+            seq.serialize_element(element)?;
+        }
+        seq.end()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use chrono::Utc;
+
+    #[test]
+    fn encoding_decoding() {
+        let r = Request {
+            filters: vec![
+                types::Filter {
+                    authors: vec![
+                        "npub1h0h2zfcp3z7acvc9l0qvpgfqznkalj77whsdkmmlwanfssys3ngsx336a8"
+                            .try_into()
+                            .expect("some addr object"),
+                    ],
+                    until: Some(Utc::now()),
+                    limit: 30,
+                    ..Default::default()
+                },
+                types::Filter {
+                    limit: 20,
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        };
+
+        let serialized = serde_json::to_string(&r).expect("valid json string");
+        let obj: Request = serde_json::from_str(&serialized).expect("valid request");
+        assert_eq!(r.subscription_id, obj.subscription_id);
+        assert_eq!(r.filters.len(), obj.filters.len());
+    }
+}

+ 2 - 0
crates/types/src/lib.rs

@@ -0,0 +1,2 @@
+pub mod client;
+pub mod types;

+ 164 - 0
crates/types/src/types/addr.rs

@@ -0,0 +1,164 @@
+use bech32::{self, FromBase32, ToBase32, Variant};
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    ser::{self, Serializer},
+};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Unexpected HRP (Human readable part)")]
+    UnexpectedHrp,
+
+    #[error("Error decoding: {0}")]
+    DecodingError(#[from] hex::FromHexError),
+
+    #[error("Data is too long ({0} bytes). Addresses are up to 32 bytes")]
+    TooLong(usize),
+
+    #[error("Bech32 error: {0}")]
+    Bech32(#[from] bech32::Error),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
+pub enum Type {
+    NPub,
+    NSec,
+    Note,
+    Unknown,
+}
+
+impl ToString for Type {
+    fn to_string(&self) -> String {
+        match *self {
+            Self::NPub => "npub".to_owned(),
+            Self::NSec => "nsec".to_owned(),
+            Self::Note => "note".to_owned(),
+            Self::Unknown => "".to_owned(),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Addr {
+    bytes: Vec<u8>,
+    pub typ: Type,
+}
+
+impl Addr {
+    pub fn try_from_public_key_str(typ: Type, pk: &str) -> Result<Self, Error> {
+        Addr::try_from_bytes(typ, hex::decode(pk)?)
+    }
+
+    pub fn try_from_bytes(typ: Type, bytes: Vec<u8>) -> Result<Self, Error> {
+        if bytes.len() < 32 {
+            return Err(Error::TooLong(bytes.len()));
+        }
+
+        Ok(Addr { bytes, typ })
+    }
+
+    pub fn change_type(self, new_type: Type) -> Self {
+        Self {
+            bytes: self.bytes,
+            typ: new_type,
+        }
+    }
+
+    pub fn to_public_key(&self) -> String {
+        hex::encode(&self.bytes)
+    }
+
+    pub fn try_to_human(&self) -> Result<String, Error> {
+        Ok(bech32::encode(
+            &self.typ.to_string(),
+            self.bytes.to_base32(),
+            Variant::Bech32,
+        )?)
+    }
+}
+
+impl TryFrom<&str> for Addr {
+    type Error = Error;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        if let Ok(bytes) = hex::decode(value) {
+            return Ok(Self {
+                bytes,
+                typ: Type::Unknown,
+            });
+        }
+        let (hrp, bytes, _) = bech32::decode(value)?;
+        let typ = match hrp.to_lowercase().as_str() {
+            "npub" => Ok(Type::NPub),
+            "nsec" => Ok(Type::NSec),
+            "note" => Ok(Type::Note),
+            _ => Err(Error::UnexpectedHrp),
+        }?;
+
+        Ok(Self {
+            bytes: Vec::<u8>::from_base32(&bytes)?,
+            typ,
+        })
+    }
+}
+
+impl<'de> Deserialize<'de> for Addr {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <&str>::deserialize(deserializer)?;
+        let t = s
+            .try_into()
+            .map_err(|e: Error| de::Error::custom(e.to_string()))?;
+        Ok(t)
+    }
+}
+
+impl ser::Serialize for Addr {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let str = self.to_public_key();
+        serializer.serialize_str(&str)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn deserializes_json() {
+        let json_str = "\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\"";
+        let x: Addr = serde_json::from_str(json_str).expect("valid pk");
+        assert_eq!(Type::Unknown, x.typ);
+        assert_eq!(
+            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr",
+            x.change_type(Type::NPub)
+                .try_to_human()
+                .expect("valid addr"),
+        );
+    }
+
+    #[test]
+    fn deserializes_from_public_key_and_json() {
+        let addr1 = Addr::try_from_public_key_str(
+            Type::NPub,
+            "b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78",
+        )
+        .expect("valid addr");
+        assert_eq!(
+            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr".to_owned(),
+            addr1.try_to_human().expect("valid address")
+        );
+
+        let addr2: Addr = serde_json::from_str(
+            "\"npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr\"",
+        )
+        .expect("valid addr");
+        assert_eq!(addr1, addr2);
+    }
+}

+ 94 - 0
crates/types/src/types/filter.rs

@@ -0,0 +1,94 @@
+use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Timestamp {0} is ambiguous (min={1}, max={2})")]
+    AmbiguousTimestamp(i64, DateTime<Utc>, DateTime<Utc>),
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone)]
+pub struct Filter {
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub ids: Vec<super::Addr>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub authors: Vec<super::Addr>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub kinds: Vec<u32>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub tags: Vec<super::Tag>,
+    #[serde(default, with = "ts_seconds", skip_serializing_if = "Option::is_none")]
+    pub since: Option<DateTime<Utc>>,
+    #[serde(default, with = "ts_seconds", skip_serializing_if = "Option::is_none")]
+    pub until: Option<DateTime<Utc>>,
+    #[serde(default, skip_serializing_if = "is_zero")]
+    pub limit: u64,
+}
+
+mod ts_seconds {
+    use super::Error;
+    use chrono::{DateTime, LocalResult, TimeZone, Utc};
+    use serde::{de, Deserialize, Deserializer, Serializer};
+
+    pub fn serialize<S>(date: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match date {
+            Some(date) => serializer.serialize_i64(date.timestamp()),
+            _ => serializer.serialize_none(),
+        }
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let timestamp: Option<i64> = Deserialize::deserialize(deserializer)?;
+        let timestamp = if let Some(timestamp) = timestamp {
+            timestamp
+        } else {
+            return Ok(None);
+        };
+
+        match Utc.timestamp_opt(timestamp, 0) {
+            LocalResult::None => Err(de::Error::custom("Invalid timestamp")),
+            LocalResult::Ambiguous(min, max) => Err(de::Error::custom(
+                Error::AmbiguousTimestamp(timestamp, min, max).to_string(),
+            )),
+            LocalResult::Single(ts) => Ok(Some(ts)),
+        }
+    }
+}
+
+#[inline]
+fn is_zero(number: &u64) -> bool {
+    *number == 0
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn deserialize() {
+        let json = "{\"authors\":[\"c0f0f1\", \"1234567901\"]}";
+        let obj: Result<Filter, _> = serde_json::from_str(&json);
+        assert!(obj.is_ok());
+    }
+
+    #[test]
+    fn serialize() {
+        let now = Utc::now();
+        let x = Filter {
+            since: Some(now),
+            ..Default::default()
+        };
+
+        assert_eq!(
+            format!("{}\"since\":{:?}{}", "{", now.timestamp(), "}"),
+            serde_json::to_string(&x).expect("valid json")
+        );
+    }
+}

+ 8 - 0
crates/types/src/types/mod.rs

@@ -0,0 +1,8 @@
+pub mod addr;
+pub mod filter;
+pub mod subscription_id;
+pub mod tag;
+
+pub use {
+    self::addr::Addr, self::filter::Filter, self::subscription_id::SubscriptionId, self::tag::Tag,
+};

+ 84 - 0
crates/types/src/types/subscription_id.rs

@@ -0,0 +1,84 @@
+use hex::ToHex;
+use rand::RngCore;
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    Serialize,
+};
+use std::convert::TryFrom;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Subscription ID is limited to 64 characters")]
+    TooLong,
+}
+
+#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
+pub struct SubscriptionId(String);
+
+impl<'de> Deserialize<'de> for SubscriptionId {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <&str>::deserialize(deserializer)?;
+        let t = s
+            .try_into()
+            .map_err(|e: Error| de::Error::custom(e.to_string()))?;
+        Ok(t)
+    }
+}
+
+impl TryFrom<&str> for SubscriptionId {
+    type Error = Error;
+    fn try_from(s: &str) -> Result<Self, Self::Error> {
+        if s.as_bytes().len() > 64 {
+            return Err(Error::TooLong);
+        }
+        Ok(SubscriptionId(s.into()))
+    }
+}
+
+impl Default for SubscriptionId {
+    fn default() -> Self {
+        let mut data = [0u8; 32];
+        rand::thread_rng().fill_bytes(&mut data);
+        Self(data.encode_hex::<String>())
+    }
+}
+
+impl ToString for SubscriptionId {
+    fn to_string(&self) -> String {
+        self.0.clone()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn default() {
+        assert_eq!(64, SubscriptionId::default().to_string().len());
+    }
+
+    #[test]
+    fn too_long() {
+        let f = format!("too_long:{}", SubscriptionId::default().to_string());
+        let r: Result<SubscriptionId, _> = f.as_str().try_into();
+        assert!(r.is_err());
+    }
+
+    #[test]
+    fn deserialize() {
+        let r: Result<SubscriptionId, _> = serde_json::from_str("\"ok\"");
+        assert_eq!("ok".to_owned(), r.expect("valid").to_string());
+    }
+
+    #[test]
+    fn deserialize_err() {
+        let f = format!("\"too_long:{}\"", SubscriptionId::default().to_string());
+        let r: Result<SubscriptionId, _> = serde_json::from_str(&f);
+        assert!(r.is_err());
+    }
+}

+ 126 - 0
crates/types/src/types/tag.rs

@@ -0,0 +1,126 @@
+use super::Addr;
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    ser::{self, SerializeSeq, Serializer},
+};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Subscription ID is limited to 64 characters")]
+    TooLong,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum TagType {
+    Event,
+    PubKey,
+    Custom(String),
+}
+
+impl From<&str> for TagType {
+    fn from(s: &str) -> Self {
+        match s {
+            "e" => TagType::Event,
+            "p" => TagType::PubKey,
+            e => TagType::Custom(e.to_owned()),
+        }
+    }
+}
+
+impl ToString for TagType {
+    fn to_string(&self) -> String {
+        match self {
+            TagType::Event => "e".to_owned(),
+            TagType::PubKey => "p".to_owned(),
+            TagType::Custom(x) => x.to_owned(),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Tag {
+    pub typ: TagType,
+    pub value: Addr,
+    pub relayer_url: Option<String>,
+    pub extra: Vec<String>,
+}
+
+impl Tag {
+    pub fn new<T: Into<TagType>>(
+        typ: T,
+        value: Addr,
+        relayer_url: Option<String>,
+        extra: Vec<String>,
+    ) -> Self {
+        Self {
+            typ: typ.into(),
+            value,
+            relayer_url,
+            extra,
+        }
+    }
+}
+
+impl ser::Serialize for Tag {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let len = if self.relayer_url.is_some() {
+            3 + self.extra.len()
+        } else {
+            2
+        };
+        let mut seq = serializer.serialize_seq(Some(len))?;
+        seq.serialize_element(&self.typ.to_string())?;
+        seq.serialize_element(&self.value.to_public_key())?;
+        if let Some(addr) = self.relayer_url.as_ref() {
+            seq.serialize_element(addr)?;
+            for extra in self.extra.iter() {
+                seq.serialize_element(extra)?;
+            }
+        }
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for Tag {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let v: Vec<String> = Deserialize::deserialize(deserializer)?;
+        if v.len() > 2 {
+            return Err(de::Error::custom("expected array of two strings"));
+        }
+        let value: Addr = v[1]
+            .as_str()
+            .try_into()
+            .map_err(|e: super::addr::Error| de::Error::custom(e.to_string()))?;
+        let relayer_url = v.get(2).map(|x| x.to_owned());
+
+        let extra = if v.len() > 3 {
+            v[3..].to_owned()
+        } else {
+            vec![]
+        };
+
+        Ok(Self::new(v[0].as_str(), value, relayer_url, extra))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn serialize_deserialize() {
+        let json = "[\"p\",\"a0b0c0d0\"]";
+        let tag: Tag = serde_json::from_str(&json).expect("valid json");
+        assert_eq!(tag.typ, TagType::PubKey);
+        assert_eq!(tag.value.to_public_key(), "a0b0c0d0".to_owned());
+        assert!(tag.relayer_url.is_none());
+        assert!(tag.extra.is_empty());
+    }
+}