1use serde_json::{Value, json};
16
17use crate::nostr_event::NostrEvent;
18
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct Filter {
24 pub ids: Vec<String>,
26 pub authors: Vec<String>,
28 pub kinds: Vec<u32>,
30 pub p_tags: Vec<String>,
32 pub since: Option<i64>,
34 pub until: Option<i64>,
36 pub limit: Option<usize>,
38}
39
40impl Filter {
41 pub fn to_json(&self) -> Value {
43 let mut m = serde_json::Map::new();
44 if !self.ids.is_empty() {
45 m.insert("ids".into(), json!(self.ids));
46 }
47 if !self.authors.is_empty() {
48 m.insert("authors".into(), json!(self.authors));
49 }
50 if !self.kinds.is_empty() {
51 m.insert("kinds".into(), json!(self.kinds));
52 }
53 if !self.p_tags.is_empty() {
54 m.insert("#p".into(), json!(self.p_tags));
55 }
56 if let Some(s) = self.since {
57 m.insert("since".into(), json!(s));
58 }
59 if let Some(u) = self.until {
60 m.insert("until".into(), json!(u));
61 }
62 if let Some(l) = self.limit {
63 m.insert("limit".into(), json!(l));
64 }
65 Value::Object(m)
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub enum ClientMessage {
72 Event(NostrEvent),
74 Req {
76 sub_id: String,
77 filters: Vec<Filter>,
78 },
79 Close(String),
81}
82
83impl ClientMessage {
84 pub fn to_json_string(&self) -> String {
86 let v = match self {
87 ClientMessage::Event(e) => json!(["EVENT", e]),
88 ClientMessage::Req { sub_id, filters } => {
89 let mut arr = vec![json!("REQ"), json!(sub_id)];
90 arr.extend(filters.iter().map(Filter::to_json));
91 Value::Array(arr)
92 }
93 ClientMessage::Close(sub) => json!(["CLOSE", sub]),
94 };
95 serde_json::to_string(&v).expect("client message always serializes")
96 }
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub enum RelayMessage {
102 Event { sub_id: String, event: NostrEvent },
104 Ok {
106 event_id: String,
107 accepted: bool,
108 message: String,
109 },
110 Eose(String),
112 Closed { sub_id: String, message: String },
114 Notice(String),
116 Unknown(String),
119}
120
121#[derive(Debug, PartialEq, Eq)]
122pub enum RelayParseError {
123 NotJson,
125 NotArray,
127 NoType,
129 BadShape,
131 BadEvent,
133}
134
135impl std::fmt::Display for RelayParseError {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 let s = match self {
138 RelayParseError::NotJson => "relay message is not JSON",
139 RelayParseError::NotArray => "relay message is not a JSON array",
140 RelayParseError::NoType => "relay message has no string type tag",
141 RelayParseError::BadShape => "relay message has the wrong shape for its type",
142 RelayParseError::BadEvent => "relay EVENT carried a malformed event",
143 };
144 write!(f, "{s}")
145 }
146}
147
148impl RelayMessage {
149 pub fn parse(s: &str) -> Result<RelayMessage, RelayParseError> {
152 let v: Value = serde_json::from_str(s).map_err(|_| RelayParseError::NotJson)?;
153 let arr = v.as_array().filter(|a| !a.is_empty());
154 let arr = arr.ok_or(RelayParseError::NotArray)?;
155 let tag = arr
156 .first()
157 .and_then(Value::as_str)
158 .ok_or(RelayParseError::NoType)?;
159 match tag {
160 "EVENT" => {
161 let sub_id = str_at(arr, 1)?;
162 let event_val = arr.get(2).ok_or(RelayParseError::BadShape)?.clone();
163 let event: NostrEvent =
164 serde_json::from_value(event_val).map_err(|_| RelayParseError::BadEvent)?;
165 Ok(RelayMessage::Event { sub_id, event })
166 }
167 "OK" => {
168 let event_id = str_at(arr, 1)?;
169 let accepted = arr
170 .get(2)
171 .and_then(Value::as_bool)
172 .ok_or(RelayParseError::BadShape)?;
173 let message = arr.get(3).and_then(Value::as_str).unwrap_or("").to_string();
176 Ok(RelayMessage::Ok {
177 event_id,
178 accepted,
179 message,
180 })
181 }
182 "EOSE" => Ok(RelayMessage::Eose(str_at(arr, 1)?)),
183 "CLOSED" => Ok(RelayMessage::Closed {
184 sub_id: str_at(arr, 1)?,
185 message: arr.get(2).and_then(Value::as_str).unwrap_or("").to_string(),
186 }),
187 "NOTICE" => Ok(RelayMessage::Notice(
188 arr.get(1).and_then(Value::as_str).unwrap_or("").to_string(),
189 )),
190 other => Ok(RelayMessage::Unknown(other.to_string())),
191 }
192 }
193}
194
195fn str_at(arr: &[Value], i: usize) -> Result<String, RelayParseError> {
197 arr.get(i)
198 .and_then(Value::as_str)
199 .map(str::to_string)
200 .ok_or(RelayParseError::BadShape)
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::nostr_event::wire_to_nostr;
207 use crate::nostr_key::generate_transport_key;
208 use crate::signing::{generate_keypair, sign_message_v31};
209
210 fn an_event() -> NostrEvent {
211 let (sk, pk) = generate_keypair();
212 let msg = json!({
213 "timestamp": "2026-06-14T12:00:00Z",
214 "from": "did:wire:slate-lotus-1",
215 "kind": 1,
216 "body": {"content": "hi"},
217 });
218 let wire = sign_message_v31(&msg, &sk, &pk, "slate-lotus").unwrap();
219 let (nsk, _x) = generate_transport_key();
220 wire_to_nostr(&wire, &nsk).unwrap()
221 }
222
223 #[test]
224 fn filter_omits_empty_fields() {
225 let f = Filter {
226 p_tags: vec!["abcd".into()],
227 kinds: vec![1, 4],
228 since: Some(1700000000),
229 ..Default::default()
230 };
231 let v = f.to_json();
232 assert_eq!(v["#p"], json!(["abcd"]));
233 assert_eq!(v["kinds"], json!([1, 4]));
234 assert_eq!(v["since"], json!(1700000000));
235 assert!(v.get("ids").is_none());
237 assert!(v.get("authors").is_none());
238 assert!(v.get("until").is_none());
239 assert!(v.get("limit").is_none());
240 }
241
242 #[test]
243 fn client_event_serializes_to_nip01() {
244 let ev = an_event();
245 let s = ClientMessage::Event(ev.clone()).to_json_string();
246 let v: Value = serde_json::from_str(&s).unwrap();
247 assert_eq!(v[0], "EVENT");
248 assert_eq!(v[1]["id"], ev.id);
249 assert_eq!(v[1]["sig"], ev.sig);
250 }
251
252 #[test]
253 fn client_req_serializes_tag_subid_and_filters() {
254 let req = ClientMessage::Req {
255 sub_id: "wire-sub-1".into(),
256 filters: vec![Filter {
257 p_tags: vec!["mypub".into()],
258 kinds: vec![1],
259 ..Default::default()
260 }],
261 };
262 let v: Value = serde_json::from_str(&req.to_json_string()).unwrap();
263 assert_eq!(v[0], "REQ");
264 assert_eq!(v[1], "wire-sub-1");
265 assert_eq!(v[2]["#p"], json!(["mypub"]));
266 }
267
268 #[test]
269 fn client_close_serializes() {
270 let v: Value =
271 serde_json::from_str(&ClientMessage::Close("s1".into()).to_json_string()).unwrap();
272 assert_eq!(v, json!(["CLOSE", "s1"]));
273 }
274
275 #[test]
276 fn parse_relay_event() {
277 let ev = an_event();
278 let s = serde_json::to_string(&json!(["EVENT", "sub-1", ev])).unwrap();
280 match RelayMessage::parse(&s).unwrap() {
281 RelayMessage::Event { sub_id, event } => {
282 assert_eq!(sub_id, "sub-1");
283 assert_eq!(event, ev);
284 }
285 other => panic!("expected Event, got {other:?}"),
286 }
287 }
288
289 #[test]
290 fn parse_ok_eose_closed_notice() {
291 assert_eq!(
292 RelayMessage::parse(r#"["OK","abc123",true,"saved"]"#).unwrap(),
293 RelayMessage::Ok {
294 event_id: "abc123".into(),
295 accepted: true,
296 message: "saved".into()
297 }
298 );
299 assert_eq!(
301 RelayMessage::parse(r#"["OK","abc123",false]"#).unwrap(),
302 RelayMessage::Ok {
303 event_id: "abc123".into(),
304 accepted: false,
305 message: String::new()
306 }
307 );
308 assert_eq!(
309 RelayMessage::parse(r#"["EOSE","sub-1"]"#).unwrap(),
310 RelayMessage::Eose("sub-1".into())
311 );
312 assert_eq!(
313 RelayMessage::parse(r#"["CLOSED","sub-1","rate-limited"]"#).unwrap(),
314 RelayMessage::Closed {
315 sub_id: "sub-1".into(),
316 message: "rate-limited".into()
317 }
318 );
319 assert_eq!(
320 RelayMessage::parse(r#"["NOTICE","hello"]"#).unwrap(),
321 RelayMessage::Notice("hello".into())
322 );
323 }
324
325 #[test]
326 fn parse_unknown_type_is_not_an_error() {
327 assert_eq!(
329 RelayMessage::parse(r#"["AUTH","challenge"]"#).unwrap(),
330 RelayMessage::Unknown("AUTH".into())
331 );
332 }
333
334 #[test]
335 fn parse_rejects_malformed() {
336 assert_eq!(
337 RelayMessage::parse("not json"),
338 Err(RelayParseError::NotJson)
339 );
340 assert_eq!(RelayMessage::parse("{}"), Err(RelayParseError::NotArray));
341 assert_eq!(RelayMessage::parse("[]"), Err(RelayParseError::NotArray));
342 assert_eq!(RelayMessage::parse("[123]"), Err(RelayParseError::NoType));
343 assert_eq!(
345 RelayMessage::parse(r#"["EVENT","sub",{"not":"an event"}]"#),
346 Err(RelayParseError::BadEvent)
347 );
348 assert_eq!(
350 RelayMessage::parse(r#"["EOSE"]"#),
351 Err(RelayParseError::BadShape)
352 );
353 }
354
355 #[test]
356 fn client_event_then_relay_event_roundtrip() {
357 let ev = an_event();
359 let published = ClientMessage::Event(ev.clone()).to_json_string();
360 let pv: Value = serde_json::from_str(&published).unwrap();
361 let echoed = serde_json::to_string(&json!(["EVENT", "s", pv[1]])).unwrap();
363 match RelayMessage::parse(&echoed).unwrap() {
364 RelayMessage::Event { event, .. } => assert_eq!(event, ev),
365 other => panic!("expected Event, got {other:?}"),
366 }
367 }
368}