search.rs (7396B)
1 use enostr::Pubkey; 2 use nostrdb::{Filter, FilterBuilder}; 3 use rmpv::Value; 4 use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter}; 5 6 #[derive(Debug, Eq, PartialEq, Clone, Hash)] 7 pub struct SearchQuery { 8 author: Option<Pubkey>, 9 pub search: String, 10 } 11 12 impl TokenSerializable for SearchQuery { 13 fn serialize_tokens(&self, writer: &mut TokenWriter) { 14 writer.write_token(&self.to_nfilter()) 15 } 16 17 fn parse_from_tokens<'a>(parser: &mut TokenParser<'a>) -> Result<Self, ParseError<'a>> { 18 if let Some(query) = SearchQuery::from_nfilter(parser.pull_token()?) { 19 Ok(query) 20 } else { 21 Err(ParseError::DecodeFailed) 22 } 23 } 24 } 25 26 impl SearchQuery { 27 pub fn new(search: String) -> Self { 28 let author: Option<Pubkey> = None; 29 Self { search, author } 30 } 31 /// Convert the query to a filter-compatible MessagePack value 32 fn to_msgpack_value(&self) -> Value { 33 let mut values: Vec<(Value, Value)> = Vec::with_capacity(2); 34 let search_str: &str = &self.search; 35 values.push(("search".into(), search_str.into())); 36 if let Some(pubkey) = self.author() { 37 values.push(( 38 "authors".into(), 39 Value::Array(vec![Value::Binary(pubkey.bytes().to_vec())]), 40 )) 41 } 42 43 Value::Map(values) 44 } 45 46 pub fn to_nfilter(&self) -> String { 47 let hrp = bech32::Hrp::parse_unchecked("nfilter"); 48 let msgpack_value = self.to_msgpack_value(); 49 let mut buf = vec![]; 50 rmpv::encode::write_value(&mut buf, &msgpack_value) 51 .expect("expected nfilter to encode ok. too big?"); 52 53 bech32::encode::<bech32::Bech32>(hrp, &buf).expect("expected bech32 nfilter to encode ok") 54 } 55 56 fn decode_value(value: &Value) -> Option<Self> { 57 let mut search: Option<String> = None; 58 let mut author: Option<Pubkey> = None; 59 60 let values = if let Value::Map(values) = value { 61 values 62 } else { 63 return None; 64 }; 65 66 for (key, value) in values { 67 let key_str: &str = if let Value::String(s) = key { 68 s.as_str()? 69 } else { 70 continue; 71 }; 72 73 if key_str == "search" { 74 if let Value::String(search_str) = value { 75 search = search_str.clone().into_str(); 76 } else { 77 continue; 78 } 79 } else if key_str == "authors" { 80 let authors = if let Value::Array(authors) = value { 81 authors 82 } else { 83 continue; 84 }; 85 86 let author_value = if let Some(author_value) = authors.first() { 87 author_value 88 } else { 89 continue; 90 }; 91 92 let author_bytes: &[u8] = if let Value::Binary(author_bytes) = author_value { 93 author_bytes 94 } else { 95 continue; 96 }; 97 98 let pubkey = Pubkey::new(author_bytes.try_into().ok()?); 99 author = Some(pubkey); 100 } 101 } 102 103 let search = search?; 104 105 Some(Self { search, author }) 106 } 107 108 pub fn filter(&self) -> FilterBuilder { 109 Filter::new().search(&self.search).kinds([1]) 110 } 111 112 pub fn from_nfilter(nfilter: &str) -> Option<Self> { 113 let (hrp, msgpack_data) = bech32::decode(nfilter).ok()?; 114 if hrp.as_str() != "nfilter" { 115 return None; 116 } 117 118 let value = rmpv::decode::read_value(&mut &msgpack_data[..]).ok()?; 119 120 Self::decode_value(&value) 121 } 122 123 pub fn author(&self) -> Option<&Pubkey> { 124 self.author.as_ref() 125 } 126 } 127 128 #[cfg(test)] 129 mod tests { 130 use super::*; 131 use enostr::Pubkey; 132 use rmpv::Value; 133 use tokenator::{TokenParser, TokenSerializable, TokenWriter}; 134 135 fn test_pubkey() -> Pubkey { 136 let bytes: [u8; 32] = [1; 32]; // Example public key 137 Pubkey::new(bytes) 138 } 139 140 #[test] 141 fn test_to_msgpack_value() { 142 let query = SearchQuery { 143 author: Some(test_pubkey()), 144 search: "nostrdb".to_string(), 145 }; 146 let msgpack_value = query.to_msgpack_value(); 147 148 if let Value::Map(values) = msgpack_value { 149 assert!(values 150 .iter() 151 .any(|(k, v)| *k == Value::String("search".into()) 152 && *v == Value::String("nostrdb".into()))); 153 assert!(values 154 .iter() 155 .any(|(k, _v)| *k == Value::String("authors".into()))); 156 } else { 157 panic!("Failed to encode SearchQuery to MessagePack"); 158 } 159 } 160 161 #[test] 162 fn test_to_nfilter() { 163 let query = SearchQuery { 164 author: Some(test_pubkey()), 165 search: "nostrdb".to_string(), 166 }; 167 let encoded = query.to_nfilter(); 168 assert!(encoded.starts_with("nfilter"), "nfilter encoding failed"); 169 } 170 171 #[test] 172 fn test_from_nfilter() { 173 let query = SearchQuery { 174 author: Some(test_pubkey()), 175 search: "nostrdb".to_string(), 176 }; 177 let encoded = query.to_nfilter(); 178 let decoded = SearchQuery::from_nfilter(&encoded).expect("Failed to decode nfilter"); 179 assert_eq!(query, decoded); 180 } 181 182 #[test] 183 fn test_nfilter_roundtrip() { 184 let queries = vec![ 185 SearchQuery { 186 author: None, 187 search: "nostrdb".to_string(), 188 }, 189 SearchQuery { 190 author: Some(test_pubkey()), 191 search: "test".to_string(), 192 }, 193 ]; 194 195 for query in queries { 196 let encoded = query.to_nfilter(); 197 let decoded = 198 SearchQuery::from_nfilter(&encoded).expect("Failed to decode valid nfilter"); 199 assert_eq!(query, decoded, "Roundtrip encoding/decoding failed"); 200 } 201 } 202 203 #[test] 204 fn test_invalid_nfilter() { 205 let invalid_nfilter = "nfilter1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; 206 assert!(SearchQuery::from_nfilter(invalid_nfilter).is_none()); 207 } 208 209 #[test] 210 fn test_invalid_hrp() { 211 let invalid_nfilter = "invalid1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; 212 assert!(SearchQuery::from_nfilter(invalid_nfilter).is_none()); 213 } 214 215 #[test] 216 fn test_parse_from_tokens() { 217 let query = SearchQuery { 218 author: Some(test_pubkey()), 219 search: "nostrdb".to_string(), 220 }; 221 let mut writer = TokenWriter::default(); 222 query.serialize_tokens(&mut writer); 223 let tokens = [writer.str()]; 224 let mut parser = TokenParser::new(&tokens); 225 226 let parsed = 227 SearchQuery::parse_from_tokens(&mut parser).expect("Failed to parse from tokens"); 228 229 assert_eq!(query, parsed); 230 } 231 232 #[test] 233 fn test_parse_from_invalid_tokens() { 234 let mut parser = TokenParser::new(&[]); 235 assert!(SearchQuery::parse_from_tokens(&mut parser).is_err()); 236 } 237 }