diff --git a/bindings/nostr-ffi/src/message/subscription.rs b/bindings/nostr-ffi/src/message/subscription.rs index 97746adcd..479e9c5ee 100644 --- a/bindings/nostr-ffi/src/message/subscription.rs +++ b/bindings/nostr-ffi/src/message/subscription.rs @@ -76,6 +76,44 @@ impl From for subscription::Alphabet { } } +#[derive(Object)] +pub struct SingleLetterTag { + inner: subscription::SingleLetterTag, +} + +impl Deref for SingleLetterTag { + type Target = subscription::SingleLetterTag; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[uniffi::export] +impl SingleLetterTag { + #[uniffi::constructor] + pub fn lowercase(character: Alphabet) -> Self { + Self { + inner: subscription::SingleLetterTag::lowercase(character.into()), + } + } + + #[uniffi::constructor] + pub fn uppercase(character: Alphabet) -> Self { + Self { + inner: subscription::SingleLetterTag::uppercase(character.into()), + } + } + + pub fn is_lowercase(&self) -> bool { + self.inner.is_lowercase() + } + + pub fn is_uppercase(&self) -> bool { + self.inner.is_uppercase() + } +} + #[derive(Clone, Object)] pub struct Filter { inner: nostr::Filter, @@ -313,16 +351,20 @@ impl Filter { Arc::new(builder) } - pub fn custom_tag(self: Arc, tag: Alphabet, content: Vec) -> Arc { + pub fn custom_tag(self: Arc, tag: Arc, content: Vec) -> Self { let mut builder = unwrap_or_clone_arc(self); - builder.inner = builder.inner.custom_tag(tag.into(), content); - Arc::new(builder) + builder.inner = builder.inner.custom_tag(**tag, content); + builder } - pub fn remove_custom_tag(self: Arc, tag: Alphabet, content: Vec) -> Arc { + pub fn remove_custom_tag( + self: Arc, + tag: Arc, + content: Vec, + ) -> Self { let mut builder = unwrap_or_clone_arc(self); - builder.inner = builder.inner.remove_custom_tag(tag.into(), content); - Arc::new(builder) + builder.inner = builder.inner.remove_custom_tag(**tag, content); + builder } pub fn is_empty(&self) -> bool { diff --git a/bindings/nostr-js/src/message/subscription.rs b/bindings/nostr-js/src/message/subscription.rs index e6bc0cef5..ac6a24888 100644 --- a/bindings/nostr-js/src/message/subscription.rs +++ b/bindings/nostr-js/src/message/subscription.rs @@ -75,6 +75,44 @@ impl From for Alphabet { } } +#[wasm_bindgen(js_name = SingleLetterTag)] +pub struct JsSingleLetterTag { + inner: SingleLetterTag, +} + +impl Deref for JsSingleLetterTag { + type Target = SingleLetterTag; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[wasm_bindgen(js_class = SingleLetterTag)] +impl JsSingleLetterTag { + pub fn lowercase(character: JsAlphabet) -> Self { + Self { + inner: SingleLetterTag::lowercase(character.into()), + } + } + + pub fn uppercase(character: JsAlphabet) -> Self { + Self { + inner: SingleLetterTag::uppercase(character.into()), + } + } + + #[wasm_bindgen(js_name = isLowercase)] + pub fn is_lowercase(&self) -> bool { + self.inner.is_lowercase() + } + + #[wasm_bindgen(js_name = isUppercase)] + pub fn is_uppercase(&self) -> bool { + self.inner.is_uppercase() + } +} + #[wasm_bindgen(js_name = SubscriptionId)] pub struct JsSubscriptionId { inner: SubscriptionId, @@ -253,12 +291,12 @@ impl JsFilter { } #[wasm_bindgen(js_name = customTag)] - pub fn custom_tag(self, tag: JsAlphabet, values: Vec) -> Self { - self.inner.custom_tag(tag.into(), values).into() + pub fn custom_tag(self, tag: &JsSingleLetterTag, values: Vec) -> Self { + self.inner.custom_tag(**tag, values).into() } #[wasm_bindgen(js_name = removeCustomTag)] - pub fn remove_custom_tag(self, tag: JsAlphabet, values: Vec) -> Self { - self.inner.remove_custom_tag(tag.into(), values).into() + pub fn remove_custom_tag(self, tag: &JsSingleLetterTag, values: Vec) -> Self { + self.inner.remove_custom_tag(**tag, values).into() } } diff --git a/crates/nostr-database/src/index.rs b/crates/nostr-database/src/index.rs index b5a6c3fd9..d39d6526c 100644 --- a/crates/nostr-database/src/index.rs +++ b/crates/nostr-database/src/index.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use nostr::event::id; use nostr::nips::nip01::Coordinate; use nostr::secp256k1::XOnlyPublicKey; -use nostr::{Alphabet, Event, EventId, Filter, GenericTagValue, Kind, Timestamp}; +use nostr::{Alphabet, Event, EventId, Filter, GenericTagValue, Kind, SingleLetterTag, Timestamp}; use thiserror::Error; use tokio::sync::RwLock; @@ -123,7 +123,7 @@ struct FilterIndex { kinds: HashSet, since: Option, until: Option, - generic_tags: HashMap>, + generic_tags: HashMap>, } impl FilterIndex { @@ -143,7 +143,7 @@ impl FilterIndex { { let identifier: GenericTagValue = GenericTagValue::String(identifier.into()); self.generic_tags - .entry(Alphabet::D) + .entry(SingleLetterTag::lowercase(Alphabet::D)) .and_modify(|list| { list.insert(identifier.clone()); }) @@ -656,7 +656,7 @@ impl DatabaseIndexes { let kind = kinds.iter().next()?; let author = authors.iter().next()?; let identifier = generic_tags - .get(&Alphabet::D)? + .get(&SingleLetterTag::lowercase(Alphabet::D))? .iter() .next() .map(|v| hash(v.to_string()))?; diff --git a/crates/nostr-database/src/tag_indexes.rs b/crates/nostr-database/src/tag_indexes.rs index 3b786d71c..979a1e05c 100644 --- a/crates/nostr-database/src/tag_indexes.rs +++ b/crates/nostr-database/src/tag_indexes.rs @@ -9,7 +9,7 @@ use std::ops::{Deref, DerefMut}; use nostr::hashes::siphash24::Hash as SipHash24; use nostr::hashes::Hash; -use nostr::{Alphabet, GenericTagValue}; +use nostr::{Alphabet, GenericTagValue, SingleLetterTag}; /// Tag Index Value Size pub const TAG_INDEX_VALUE_SIZE: usize = 8; @@ -17,11 +17,11 @@ pub const TAG_INDEX_VALUE_SIZE: usize = 8; /// Tag Indexes #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct TagIndexes { - inner: BTreeMap, + inner: BTreeMap, } impl Deref for TagIndexes { - type Target = BTreeMap; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.inner @@ -37,7 +37,7 @@ impl DerefMut for TagIndexes { impl TagIndexes { /// Get hashed `d` tag pub fn identifier(&self) -> Option<[u8; TAG_INDEX_VALUE_SIZE]> { - let values = self.inner.get(&Alphabet::D)?; + let values = self.inner.get(&SingleLetterTag::lowercase(Alphabet::D))?; values.iter().next().copied() } } @@ -50,9 +50,12 @@ where fn from(iter: I) -> Self { let mut tag_index: TagIndexes = TagIndexes::default(); for t in iter.filter(|t| t.len() > 1) { - if let Some(tagnamechar) = single_char_tagname(t[0].as_ref()) { + if let Some(single_letter_tag) = single_char_tagname(t[0].as_ref()) { let inner = hash(t[1].as_ref()); - tag_index.entry(tagnamechar).or_default().insert(inner); + tag_index + .entry(single_letter_tag) + .or_default() + .insert(inner); } } tag_index @@ -60,11 +63,11 @@ where } #[inline] -fn single_char_tagname(tagname: &str) -> Option { +fn single_char_tagname(tagname: &str) -> Option { tagname .chars() .next() - .and_then(|first| Alphabet::try_from(first).ok()) + .and_then(|first| SingleLetterTag::from_char(first).ok()) } #[inline] diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs index 7e77a9103..18a0d645b 100644 --- a/crates/nostr/src/lib.rs +++ b/crates/nostr/src/lib.rs @@ -53,7 +53,8 @@ pub use self::event::{ }; pub use self::key::Keys; pub use self::message::{ - Alphabet, ClientMessage, Filter, GenericTagValue, RawRelayMessage, RelayMessage, SubscriptionId, + Alphabet, ClientMessage, Filter, GenericTagValue, RawRelayMessage, RelayMessage, + SingleLetterTag, SubscriptionId, }; pub use self::nips::nip19::{FromBech32, ToBech32}; pub use self::types::{Contact, Metadata, Timestamp, UncheckedUrl, Url}; diff --git a/crates/nostr/src/message/mod.rs b/crates/nostr/src/message/mod.rs index ad39adecc..2337fbd62 100644 --- a/crates/nostr/src/message/mod.rs +++ b/crates/nostr/src/message/mod.rs @@ -12,7 +12,7 @@ pub mod subscription; pub use self::client::ClientMessage; pub use self::relay::{RawRelayMessage, RelayMessage}; -pub use self::subscription::{Alphabet, Filter, GenericTagValue, SubscriptionId}; +pub use self::subscription::{Alphabet, Filter, GenericTagValue, SingleLetterTag, SubscriptionId}; use crate::event; /// Messages error diff --git a/crates/nostr/src/message/subscription.rs b/crates/nostr/src/message/subscription.rs index 6d15f3528..a01e96524 100644 --- a/crates/nostr/src/message/subscription.rs +++ b/crates/nostr/src/message/subscription.rs @@ -9,7 +9,6 @@ use alloc::collections::{BTreeMap as AllocMap, BTreeSet as AllocSet}; use alloc::string::{String, ToString}; use core::fmt; -use core::str::FromStr; #[cfg(feature = "std")] use std::collections::{HashMap as AllocMap, HashSet as AllocSet}; @@ -22,21 +21,22 @@ use bitcoin::secp256k1::XOnlyPublicKey; use serde::de::{Deserializer, MapAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::{EventId, JsonUtil, Kind, Timestamp}; +type GenericTags = AllocMap>; + /// Alphabet Error #[derive(Debug)] -pub enum AlphabetError { +pub enum SingleLetterTagError { /// Invalid char InvalidChar, } #[cfg(feature = "std")] -impl std::error::Error for AlphabetError {} +impl std::error::Error for SingleLetterTagError {} -impl fmt::Display for AlphabetError { +impl fmt::Display for SingleLetterTagError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidChar => write!(f, "invalid alphabet char"), @@ -76,135 +76,166 @@ pub enum Alphabet { Z, } -impl Alphabet { - /// Get as char - pub fn as_char(&self) -> char { - match self { - Self::A => 'a', - Self::B => 'b', - Self::C => 'c', - Self::D => 'd', - Self::E => 'e', - Self::F => 'f', - Self::G => 'g', - Self::H => 'h', - Self::I => 'i', - Self::J => 'j', - Self::K => 'k', - Self::L => 'l', - Self::M => 'm', - Self::N => 'n', - Self::O => 'o', - Self::P => 'p', - Self::Q => 'q', - Self::R => 'r', - Self::S => 's', - Self::T => 't', - Self::U => 'u', - Self::V => 'v', - Self::W => 'w', - Self::X => 'x', - Self::Y => 'y', - Self::Z => 'z', +/// Single-Letter Tag (a-zA-Z) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SingleLetterTag { + /// Single-letter char + pub character: Alphabet, + /// Is the `character` uppercase? + pub uppercase: bool, +} + +impl SingleLetterTag { + /// Compose new `lowercase` single-letter tag + pub fn lowercase(character: Alphabet) -> Self { + Self { + character, + uppercase: false, } } -} -impl fmt::Display for Alphabet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::A => write!(f, "a"), - Self::B => write!(f, "b"), - Self::C => write!(f, "c"), - Self::D => write!(f, "d"), - Self::E => write!(f, "e"), - Self::F => write!(f, "f"), - Self::G => write!(f, "g"), - Self::H => write!(f, "h"), - Self::I => write!(f, "i"), - Self::J => write!(f, "j"), - Self::K => write!(f, "k"), - Self::L => write!(f, "l"), - Self::M => write!(f, "m"), - Self::N => write!(f, "n"), - Self::O => write!(f, "o"), - Self::P => write!(f, "p"), - Self::Q => write!(f, "q"), - Self::R => write!(f, "r"), - Self::S => write!(f, "s"), - Self::T => write!(f, "t"), - Self::U => write!(f, "u"), - Self::V => write!(f, "v"), - Self::W => write!(f, "w"), - Self::X => write!(f, "x"), - Self::Y => write!(f, "y"), - Self::Z => write!(f, "z"), + /// Compose new `uppercase` single-letter tag + pub fn uppercase(character: Alphabet) -> Self { + Self { + character, + uppercase: true, } } -} -impl TryFrom for Alphabet { - type Error = AlphabetError; - - fn try_from(c: char) -> Result { - match c { - 'a' => Ok(Self::A), - 'b' => Ok(Self::B), - 'c' => Ok(Self::C), - 'd' => Ok(Self::D), - 'e' => Ok(Self::E), - 'f' => Ok(Self::F), - 'g' => Ok(Self::G), - 'h' => Ok(Self::H), - 'i' => Ok(Self::I), - 'j' => Ok(Self::J), - 'k' => Ok(Self::K), - 'l' => Ok(Self::L), - 'm' => Ok(Self::M), - 'n' => Ok(Self::N), - 'o' => Ok(Self::O), - 'p' => Ok(Self::P), - 'q' => Ok(Self::Q), - 'r' => Ok(Self::R), - 's' => Ok(Self::S), - 't' => Ok(Self::T), - 'u' => Ok(Self::U), - 'v' => Ok(Self::V), - 'w' => Ok(Self::W), - 'x' => Ok(Self::X), - 'y' => Ok(Self::Y), - 'z' => Ok(Self::Z), - _ => Err(AlphabetError::InvalidChar), + /// Parse single-letter tag from [char] + pub fn from_char(c: char) -> Result { + let character = match c { + 'a' | 'A' => Alphabet::A, + 'b' | 'B' => Alphabet::B, + 'c' | 'C' => Alphabet::C, + 'd' | 'D' => Alphabet::D, + 'e' | 'E' => Alphabet::E, + 'f' | 'F' => Alphabet::F, + 'g' | 'G' => Alphabet::G, + 'h' | 'H' => Alphabet::H, + 'i' | 'I' => Alphabet::I, + 'j' | 'J' => Alphabet::J, + 'k' | 'K' => Alphabet::K, + 'l' | 'L' => Alphabet::L, + 'm' | 'M' => Alphabet::M, + 'n' | 'N' => Alphabet::N, + 'o' | 'O' => Alphabet::O, + 'p' | 'P' => Alphabet::P, + 'q' | 'Q' => Alphabet::Q, + 'r' | 'R' => Alphabet::R, + 's' | 'S' => Alphabet::S, + 't' | 'T' => Alphabet::T, + 'u' | 'U' => Alphabet::U, + 'v' | 'V' => Alphabet::V, + 'w' | 'W' => Alphabet::W, + 'x' | 'X' => Alphabet::X, + 'y' | 'Y' => Alphabet::Y, + 'z' | 'Z' => Alphabet::Z, + _ => return Err(SingleLetterTagError::InvalidChar), + }; + + Ok(Self { + character, + uppercase: c.is_uppercase(), + }) + } + + /// Get as char + pub fn as_char(&self) -> char { + if self.uppercase { + match self.character { + Alphabet::A => 'A', + Alphabet::B => 'B', + Alphabet::C => 'C', + Alphabet::D => 'D', + Alphabet::E => 'E', + Alphabet::F => 'F', + Alphabet::G => 'G', + Alphabet::H => 'H', + Alphabet::I => 'I', + Alphabet::J => 'J', + Alphabet::K => 'K', + Alphabet::L => 'L', + Alphabet::M => 'M', + Alphabet::N => 'N', + Alphabet::O => 'O', + Alphabet::P => 'P', + Alphabet::Q => 'Q', + Alphabet::R => 'R', + Alphabet::S => 'S', + Alphabet::T => 'T', + Alphabet::U => 'U', + Alphabet::V => 'V', + Alphabet::W => 'W', + Alphabet::X => 'X', + Alphabet::Y => 'Y', + Alphabet::Z => 'Z', + } + } else { + match self.character { + Alphabet::A => 'a', + Alphabet::B => 'b', + Alphabet::C => 'c', + Alphabet::D => 'd', + Alphabet::E => 'e', + Alphabet::F => 'f', + Alphabet::G => 'g', + Alphabet::H => 'h', + Alphabet::I => 'i', + Alphabet::J => 'j', + Alphabet::K => 'k', + Alphabet::L => 'l', + Alphabet::M => 'm', + Alphabet::N => 'n', + Alphabet::O => 'o', + Alphabet::P => 'p', + Alphabet::Q => 'q', + Alphabet::R => 'r', + Alphabet::S => 's', + Alphabet::T => 't', + Alphabet::U => 'u', + Alphabet::V => 'v', + Alphabet::W => 'w', + Alphabet::X => 'x', + Alphabet::Y => 'y', + Alphabet::Z => 'z', + } } } -} -impl FromStr for Alphabet { - type Err = AlphabetError; + /// Check if single-letter tag is `lowercase` + pub fn is_lowercase(&self) -> bool { + !self.uppercase + } - fn from_str(s: &str) -> Result { - let c: char = s.chars().next().ok_or(AlphabetError::InvalidChar)?; - Self::try_from(c) + /// Check if single-letter tag is `uppercase` + pub fn is_uppercase(&self) -> bool { + self.uppercase } } -impl Serialize for Alphabet { +impl fmt::Display for SingleLetterTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "#{}", self.as_char()) + } +} + +impl Serialize for SingleLetterTag { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_char(self.as_char()) } } -impl<'de> Deserialize<'de> for Alphabet { +impl<'de> Deserialize<'de> for SingleLetterTag { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let value = Value::deserialize(deserializer)?; - let alphaber: String = serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Self::from_str(&alphaber).map_err(serde::de::Error::custom) + let character: char = char::deserialize(deserializer)?; + Self::from_char(character).map_err(serde::de::Error::custom) } } @@ -260,8 +291,7 @@ impl<'de> Deserialize<'de> for SubscriptionId { where D: Deserializer<'de>, { - let value = Value::deserialize(deserializer)?; - let id: String = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + let id: String = String::deserialize(deserializer)?; Ok(Self::new(id)) } } @@ -357,7 +387,7 @@ pub struct Filter { deserialize_with = "deserialize_generic_tags" )] #[serde(default)] - pub generic_tags: AllocMap>, + pub generic_tags: GenericTags, } impl Filter { @@ -386,7 +416,7 @@ impl Filter { where I: IntoIterator, { - for id in ids { + for id in ids.into_iter() { self.ids.remove(&id); } self @@ -412,7 +442,7 @@ impl Filter { where I: IntoIterator, { - for author in authors { + for author in authors.into_iter() { self.authors.remove(&author); } self @@ -438,7 +468,7 @@ impl Filter { where I: IntoIterator, { - for kind in kinds { + for kind in kinds.into_iter() { self.kinds.remove(&kind); } self @@ -446,7 +476,7 @@ impl Filter { /// Add event pub fn event(self, id: EventId) -> Self { - self.custom_tag(Alphabet::E, vec![id]) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::E), [id]) } /// Add events @@ -454,7 +484,7 @@ impl Filter { where I: IntoIterator, { - self.custom_tag(Alphabet::E, events) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::E), events) } /// Remove events @@ -462,12 +492,12 @@ impl Filter { where I: IntoIterator, { - self.remove_custom_tag(Alphabet::E, events) + self.remove_custom_tag(SingleLetterTag::lowercase(Alphabet::E), events) } /// Add pubkey pub fn pubkey(self, pubkey: XOnlyPublicKey) -> Self { - self.custom_tag(Alphabet::P, vec![pubkey]) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::P), [pubkey]) } /// Add pubkeys @@ -475,7 +505,7 @@ impl Filter { where I: IntoIterator, { - self.custom_tag(Alphabet::P, pubkeys) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::P), pubkeys) } /// Remove pubkeys @@ -483,7 +513,7 @@ impl Filter { where I: IntoIterator, { - self.remove_custom_tag(Alphabet::P, pubkeys) + self.remove_custom_tag(SingleLetterTag::lowercase(Alphabet::P), pubkeys) } /// Add hashtag @@ -493,7 +523,7 @@ impl Filter { where S: Into, { - self.custom_tag(Alphabet::T, vec![hashtag.into()]) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::T), [hashtag.into()]) } /// Add hashtags @@ -504,7 +534,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.custom_tag(Alphabet::T, hashtags.into_iter().map(|s| s.into())) + self.custom_tag( + SingleLetterTag::lowercase(Alphabet::T), + hashtags.into_iter().map(|s| s.into()), + ) } /// Remove hashtags @@ -513,7 +546,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.remove_custom_tag(Alphabet::T, hashtags.into_iter().map(|s| s.into())) + self.remove_custom_tag( + SingleLetterTag::lowercase(Alphabet::T), + hashtags.into_iter().map(|s| s.into()), + ) } /// Add reference @@ -523,7 +559,7 @@ impl Filter { where S: Into, { - self.custom_tag(Alphabet::R, vec![reference.into()]) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::R), [reference.into()]) } /// Add references @@ -534,7 +570,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.custom_tag(Alphabet::R, references.into_iter().map(|s| s.into())) + self.custom_tag( + SingleLetterTag::lowercase(Alphabet::R), + references.into_iter().map(|s| s.into()), + ) } /// Remove references @@ -543,7 +582,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.remove_custom_tag(Alphabet::R, references.into_iter().map(|s| s.into())) + self.remove_custom_tag( + SingleLetterTag::lowercase(Alphabet::R), + references.into_iter().map(|s| s.into()), + ) } /// Add identifier @@ -553,7 +595,7 @@ impl Filter { where S: Into, { - self.custom_tag(Alphabet::D, vec![identifier.into()]) + self.custom_tag(SingleLetterTag::lowercase(Alphabet::D), [identifier.into()]) } /// Add identifiers @@ -564,7 +606,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.custom_tag(Alphabet::D, identifiers.into_iter().map(|s| s.into())) + self.custom_tag( + SingleLetterTag::lowercase(Alphabet::D), + identifiers.into_iter().map(|s| s.into()), + ) } /// Remove identifiers @@ -573,7 +618,10 @@ impl Filter { I: IntoIterator, S: Into, { - self.remove_custom_tag(Alphabet::D, identifiers.into_iter().map(|s| s.into())) + self.remove_custom_tag( + SingleLetterTag::lowercase(Alphabet::D), + identifiers.into_iter().map(|s| s.into()), + ) } /// Add search field @@ -644,7 +692,7 @@ impl Filter { } /// Add custom tag - pub fn custom_tag(mut self, tag: Alphabet, values: I) -> Self + pub fn custom_tag(mut self, tag: SingleLetterTag, values: I) -> Self where I: IntoIterator, T: IntoGenericTagValue, @@ -663,7 +711,7 @@ impl Filter { } /// Remove custom tag - pub fn remove_custom_tag(mut self, tag: Alphabet, values: I) -> Self + pub fn remove_custom_tag(mut self, tag: SingleLetterTag, values: I) -> Self where I: IntoIterator, T: IntoGenericTagValue, @@ -688,30 +736,25 @@ impl JsonUtil for Filter { type Err = serde_json::Error; } -fn serialize_generic_tags( - generic_tags: &AllocMap>, - serializer: S, -) -> Result +fn serialize_generic_tags(generic_tags: &GenericTags, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(Some(generic_tags.len()))?; for (tag, values) in generic_tags.iter() { - map.serialize_entry(&format!("#{tag}"), values)?; + map.serialize_entry(&tag.to_string(), values)?; } map.end() } -fn deserialize_generic_tags<'de, D>( - deserializer: D, -) -> Result>, D::Error> +fn deserialize_generic_tags<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { struct GenericTagsVisitor; impl<'de> Visitor<'de> for GenericTagsVisitor { - type Value = AllocMap>; + type Value = GenericTags; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("map in which the keys are \"#X\" for some character X") @@ -725,14 +768,23 @@ where while let Some(key) = map.next_key::()? { let mut chars = key.chars(); if let (Some('#'), Some(ch), None) = (chars.next(), chars.next(), chars.next()) { - let tag: Alphabet = Alphabet::from_str(ch.to_string().as_str()) - .map_err(serde::de::Error::custom)?; + let tag: SingleLetterTag = + SingleLetterTag::from_char(ch).map_err(serde::de::Error::custom)?; let mut values: AllocSet = map.next_value()?; - match tag { - Alphabet::P => values.retain(|v| matches!(v, GenericTagValue::Pubkey(_))), - Alphabet::E => values.retain(|v| matches!(v, GenericTagValue::EventId(_))), - _ => {} + // Check if char is lowercase + if tag.is_lowercase() { + match tag.character { + Alphabet::P => { + values.retain(|v| matches!(v, GenericTagValue::Pubkey(_))) + } + Alphabet::E => { + values.retain(|v| matches!(v, GenericTagValue::EventId(_))) + } + _ => {} + } + } else if tag.character == Alphabet::P { + values.retain(|v| matches!(v, GenericTagValue::Pubkey(_))) } generic_tags.insert(tag, values); @@ -749,6 +801,8 @@ where #[cfg(test)] mod test { + use core::str::FromStr; + use super::*; #[test] @@ -757,14 +811,14 @@ mod test { .kind(Kind::Metadata) .kind(Kind::TextNote) .kind(Kind::ContactList) - .kinds(vec![ + .kinds([ Kind::EncryptedDirectMessage, Kind::Metadata, Kind::LongFormTextNote, ]); assert_eq!( filter, - Filter::new().kinds(vec![ + Filter::new().kinds([ Kind::Metadata, Kind::TextNote, Kind::ContactList, @@ -780,28 +834,29 @@ mod test { EventId::from_hex("70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5") .unwrap(); let filter = Filter::new().id(EventId::all_zeros()).id(event_id); - let filter = filter.remove_ids(vec![EventId::all_zeros()]); + let filter = filter.remove_ids([EventId::all_zeros()]); assert_eq!(filter, Filter::new().id(event_id)); } #[test] fn test_remove_custom_tag() { - let filter = Filter::new().custom_tag(Alphabet::C, vec!["test", "test2"]); - let filter = filter.remove_custom_tag(Alphabet::C, vec!["test2"]); - assert_eq!(filter, Filter::new().custom_tag(Alphabet::C, vec!["test"])); + let filter = + Filter::new().custom_tag(SingleLetterTag::lowercase(Alphabet::C), ["test", "test2"]); + let filter = filter.remove_custom_tag(SingleLetterTag::lowercase(Alphabet::C), ["test2"]); + assert_eq!( + filter, + Filter::new().custom_tag(SingleLetterTag::lowercase(Alphabet::C), ["test"]) + ); } #[test] fn test_add_remove_event_tag() { let mut filter = Filter::new().identifier("myidentifier"); - filter = filter.custom_tag(Alphabet::D, vec!["mysecondid"]); - filter = filter.identifiers(vec!["test", "test2"]); - filter = filter.remove_custom_tag(Alphabet::D, vec!["test2"]); - filter = filter.remove_identifiers(vec!["mysecondid"]); - assert_eq!( - filter, - Filter::new().identifiers(vec!["myidentifier", "test"]) - ); + filter = filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), ["mysecondid"]); + filter = filter.identifiers(["test", "test2"]); + filter = filter.remove_custom_tag(SingleLetterTag::lowercase(Alphabet::D), ["test2"]); + filter = filter.remove_identifiers(["mysecondid"]); + assert_eq!(filter, Filter::new().identifiers(["myidentifier", "test"])); } #[test] @@ -810,15 +865,26 @@ mod test { let filter = Filter::new() .identifier("identifier") .search("test") - .custom_tag(Alphabet::J, vec!["test1"]) + .custom_tag(SingleLetterTag::lowercase(Alphabet::J), ["test1"]) .custom_tag( - Alphabet::P, - vec!["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"], + SingleLetterTag::lowercase(Alphabet::P), + ["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"], ); let json = r##"{"search":"test","#d":["identifier"],"#j":["test1"],"#p":["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"]}"##; assert_eq!(filter.as_json(), json.to_string()); } + #[test] + fn test_filter_serialization_with_uppercase_tag() { + let filter = Filter::new().custom_tag( + SingleLetterTag::uppercase(Alphabet::P), + ["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"], + ); + let json = + r##"{"#P":["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"]}"##; + assert_eq!(filter.as_json(), json); + } + #[test] fn test_filter_deserialization() { let json = r##"{"#a":["...", "test"],"#p":["379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"],"search":"test","ids":["70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5"]}"##; @@ -833,9 +899,12 @@ mod test { assert_eq!( filter, Filter::new() - .ids(vec![event_id]) + .ids([event_id]) .search("test") - .custom_tag(Alphabet::A, vec!["...".to_string(), "test".to_string()]) + .custom_tag( + SingleLetterTag::lowercase(Alphabet::A), + ["...".to_string(), "test".to_string()] + ) .pubkey(pubkey) );