1use std::fmt;
12
13use serde::de::{self, SeqAccess, Visitor};
14use serde::ser::{SerializeSeq, Serializer};
15use serde::{Deserialize, Deserializer, Serialize};
16use thiserror::Error;
17
18use super::subscription_id::SubscriptionId;
19use crate::event::Event;
20use crate::filter::Filter;
21
22const TAG_EVENT: &str = "EVENT";
23const TAG_REQ: &str = "REQ";
24const TAG_CLOSE: &str = "CLOSE";
25const TAG_AUTH: &str = "AUTH";
26const TAG_COUNT: &str = "COUNT";
27const TAG_NEG_OPEN: &str = "NEG-OPEN";
28const TAG_NEG_MSG: &str = "NEG-MSG";
29const TAG_NEG_CLOSE: &str = "NEG-CLOSE";
30
31#[derive(Debug, Clone, Error)]
33#[non_exhaustive]
34pub enum ClientMessageError {
35 #[error("client message must not be empty")]
37 Empty,
38 #[error("unknown client message tag `{0}`")]
40 UnknownTag(String),
41 #[error("malformed `{tag}` message: {reason}")]
43 Malformed {
44 tag: &'static str,
46 reason: String,
48 },
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum ClientMessage {
55 Event(Event),
59 Req {
63 subscription_id: SubscriptionId,
65 filters: Vec<Filter>,
67 },
68 Close(SubscriptionId),
72 Auth(Event),
76 Count {
80 subscription_id: SubscriptionId,
82 filter: Filter,
84 },
85 NegOpen {
90 subscription_id: SubscriptionId,
93 filter: Filter,
95 initial_message: String,
97 },
98 NegMsg {
102 subscription_id: SubscriptionId,
105 message: String,
107 },
108 NegClose {
112 subscription_id: SubscriptionId,
114 },
115}
116
117impl ClientMessage {
118 #[must_use]
120 pub const fn event(event: Event) -> Self {
121 Self::Event(event)
122 }
123
124 #[must_use]
126 pub const fn req(subscription_id: SubscriptionId, filters: Vec<Filter>) -> Self {
127 Self::Req {
128 subscription_id,
129 filters,
130 }
131 }
132
133 #[must_use]
135 pub const fn close(subscription_id: SubscriptionId) -> Self {
136 Self::Close(subscription_id)
137 }
138
139 #[must_use]
141 pub const fn auth(event: Event) -> Self {
142 Self::Auth(event)
143 }
144
145 #[must_use]
147 pub const fn count(subscription_id: SubscriptionId, filter: Filter) -> Self {
148 Self::Count {
149 subscription_id,
150 filter,
151 }
152 }
153
154 #[must_use]
156 pub const fn neg_open(
157 subscription_id: SubscriptionId,
158 filter: Filter,
159 initial_message: String,
160 ) -> Self {
161 Self::NegOpen {
162 subscription_id,
163 filter,
164 initial_message,
165 }
166 }
167
168 #[must_use]
170 pub const fn neg_msg(subscription_id: SubscriptionId, message: String) -> Self {
171 Self::NegMsg {
172 subscription_id,
173 message,
174 }
175 }
176
177 #[must_use]
179 pub const fn neg_close(subscription_id: SubscriptionId) -> Self {
180 Self::NegClose { subscription_id }
181 }
182}
183
184impl Serialize for ClientMessage {
185 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
186 where
187 S: Serializer,
188 {
189 match self {
190 Self::Event(event) => {
191 let mut seq = serializer.serialize_seq(Some(2))?;
192 seq.serialize_element(TAG_EVENT)?;
193 seq.serialize_element(event)?;
194 seq.end()
195 }
196 Self::Req {
197 subscription_id,
198 filters,
199 } => {
200 let mut seq = serializer.serialize_seq(Some(2 + filters.len()))?;
201 seq.serialize_element(TAG_REQ)?;
202 seq.serialize_element(subscription_id)?;
203 for f in filters {
204 seq.serialize_element(f)?;
205 }
206 seq.end()
207 }
208 Self::Close(id) => {
209 let mut seq = serializer.serialize_seq(Some(2))?;
210 seq.serialize_element(TAG_CLOSE)?;
211 seq.serialize_element(id)?;
212 seq.end()
213 }
214 Self::Auth(event) => {
215 let mut seq = serializer.serialize_seq(Some(2))?;
216 seq.serialize_element(TAG_AUTH)?;
217 seq.serialize_element(event)?;
218 seq.end()
219 }
220 Self::Count {
221 subscription_id,
222 filter,
223 } => {
224 let mut seq = serializer.serialize_seq(Some(3))?;
225 seq.serialize_element(TAG_COUNT)?;
226 seq.serialize_element(subscription_id)?;
227 seq.serialize_element(filter)?;
228 seq.end()
229 }
230 Self::NegOpen {
231 subscription_id,
232 filter,
233 initial_message,
234 } => {
235 let mut seq = serializer.serialize_seq(Some(4))?;
236 seq.serialize_element(TAG_NEG_OPEN)?;
237 seq.serialize_element(subscription_id)?;
238 seq.serialize_element(filter)?;
239 seq.serialize_element(initial_message)?;
240 seq.end()
241 }
242 Self::NegMsg {
243 subscription_id,
244 message,
245 } => {
246 let mut seq = serializer.serialize_seq(Some(3))?;
247 seq.serialize_element(TAG_NEG_MSG)?;
248 seq.serialize_element(subscription_id)?;
249 seq.serialize_element(message)?;
250 seq.end()
251 }
252 Self::NegClose { subscription_id } => {
253 let mut seq = serializer.serialize_seq(Some(2))?;
254 seq.serialize_element(TAG_NEG_CLOSE)?;
255 seq.serialize_element(subscription_id)?;
256 seq.end()
257 }
258 }
259 }
260}
261
262impl<'de> Deserialize<'de> for ClientMessage {
263 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
264 where
265 D: Deserializer<'de>,
266 {
267 struct ClientVisitor;
268
269 impl<'de> Visitor<'de> for ClientVisitor {
270 type Value = ClientMessage;
271
272 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 f.write_str("a Nostr client message array")
274 }
275
276 fn visit_seq<A>(self, mut seq: A) -> Result<ClientMessage, A::Error>
277 where
278 A: SeqAccess<'de>,
279 {
280 let tag: String = seq
281 .next_element()?
282 .ok_or_else(|| de::Error::custom(ClientMessageError::Empty))?;
283 match tag.as_str() {
284 TAG_EVENT => decode_event(&mut seq),
285 TAG_REQ => decode_req(&mut seq),
286 TAG_CLOSE => decode_close(&mut seq),
287 TAG_AUTH => decode_auth(&mut seq),
288 TAG_COUNT => decode_count(&mut seq),
289 TAG_NEG_OPEN => decode_neg_open(&mut seq),
290 TAG_NEG_MSG => decode_neg_msg(&mut seq),
291 TAG_NEG_CLOSE => decode_neg_close(&mut seq),
292 other => Err(de::Error::custom(ClientMessageError::UnknownTag(
293 other.to_owned(),
294 ))),
295 }
296 }
297 }
298
299 deserializer.deserialize_seq(ClientVisitor)
300 }
301}
302
303fn decode_event<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
304where
305 A: SeqAccess<'de>,
306{
307 let event: Event = seq.next_element()?.ok_or_else(|| {
308 de::Error::custom(ClientMessageError::Malformed {
309 tag: TAG_EVENT,
310 reason: "missing event".to_owned(),
311 })
312 })?;
313 Ok(ClientMessage::Event(event))
314}
315
316fn decode_req<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
317where
318 A: SeqAccess<'de>,
319{
320 let subscription_id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
321 de::Error::custom(ClientMessageError::Malformed {
322 tag: TAG_REQ,
323 reason: "missing subscription id".to_owned(),
324 })
325 })?;
326 let mut filters = Vec::new();
327 while let Some(filter) = seq.next_element::<Filter>()? {
328 filters.push(filter);
329 }
330 if filters.is_empty() {
331 return Err(de::Error::custom(ClientMessageError::Malformed {
332 tag: TAG_REQ,
333 reason: "REQ requires at least one filter".to_owned(),
334 }));
335 }
336 Ok(ClientMessage::Req {
337 subscription_id,
338 filters,
339 })
340}
341
342fn decode_close<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
343where
344 A: SeqAccess<'de>,
345{
346 let id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
347 de::Error::custom(ClientMessageError::Malformed {
348 tag: TAG_CLOSE,
349 reason: "missing subscription id".to_owned(),
350 })
351 })?;
352 Ok(ClientMessage::Close(id))
353}
354
355fn decode_auth<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
356where
357 A: SeqAccess<'de>,
358{
359 let event: Event = seq.next_element()?.ok_or_else(|| {
360 de::Error::custom(ClientMessageError::Malformed {
361 tag: TAG_AUTH,
362 reason: "missing event".to_owned(),
363 })
364 })?;
365 Ok(ClientMessage::Auth(event))
366}
367
368fn decode_count<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
369where
370 A: SeqAccess<'de>,
371{
372 let subscription_id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
373 de::Error::custom(ClientMessageError::Malformed {
374 tag: TAG_COUNT,
375 reason: "missing subscription id".to_owned(),
376 })
377 })?;
378 let filter: Filter = seq.next_element()?.ok_or_else(|| {
379 de::Error::custom(ClientMessageError::Malformed {
380 tag: TAG_COUNT,
381 reason: "missing filter".to_owned(),
382 })
383 })?;
384 Ok(ClientMessage::Count {
385 subscription_id,
386 filter,
387 })
388}
389
390fn decode_neg_open<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
391where
392 A: SeqAccess<'de>,
393{
394 let subscription_id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
395 de::Error::custom(ClientMessageError::Malformed {
396 tag: TAG_NEG_OPEN,
397 reason: "missing subscription id".to_owned(),
398 })
399 })?;
400 let filter: Filter = seq.next_element()?.ok_or_else(|| {
401 de::Error::custom(ClientMessageError::Malformed {
402 tag: TAG_NEG_OPEN,
403 reason: "missing filter".to_owned(),
404 })
405 })?;
406 let initial_message: String = seq.next_element()?.ok_or_else(|| {
413 de::Error::custom(ClientMessageError::Malformed {
414 tag: TAG_NEG_OPEN,
415 reason: "missing initial message".to_owned(),
416 })
417 })?;
418 Ok(ClientMessage::NegOpen {
419 subscription_id,
420 filter,
421 initial_message,
422 })
423}
424
425fn decode_neg_msg<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
426where
427 A: SeqAccess<'de>,
428{
429 let subscription_id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
430 de::Error::custom(ClientMessageError::Malformed {
431 tag: TAG_NEG_MSG,
432 reason: "missing subscription id".to_owned(),
433 })
434 })?;
435 let message: String = seq.next_element()?.ok_or_else(|| {
436 de::Error::custom(ClientMessageError::Malformed {
437 tag: TAG_NEG_MSG,
438 reason: "missing message".to_owned(),
439 })
440 })?;
441 Ok(ClientMessage::NegMsg {
442 subscription_id,
443 message,
444 })
445}
446
447fn decode_neg_close<'de, A>(seq: &mut A) -> Result<ClientMessage, A::Error>
448where
449 A: SeqAccess<'de>,
450{
451 let subscription_id: SubscriptionId = seq.next_element()?.ok_or_else(|| {
452 de::Error::custom(ClientMessageError::Malformed {
453 tag: TAG_NEG_CLOSE,
454 reason: "missing subscription id".to_owned(),
455 })
456 })?;
457 Ok(ClientMessage::NegClose { subscription_id })
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use crate::Keys;
464 use crate::event::EventBuilder;
465 use crate::types::Timestamp;
466 use crate::{Kind, Tag};
467
468 fn keys() -> Keys {
469 Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
470 }
471
472 fn signed_event() -> Event {
473 EventBuilder::text_note("hello")
474 .tag(Tag::new(["alt", "test"]).unwrap())
475 .created_at(Timestamp::from_secs(1_700_000_000))
476 .sign_with_keys(&keys())
477 .unwrap()
478 }
479
480 #[test]
481 fn event_round_trip() {
482 let msg = ClientMessage::event(signed_event());
483 let json = serde_json::to_string(&msg).unwrap();
484 assert!(json.starts_with("[\"EVENT\","));
485 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
486 assert_eq!(parsed, msg);
487 }
488
489 #[test]
490 fn req_round_trip() {
491 let msg = ClientMessage::req(
492 SubscriptionId::new("sub-1").unwrap(),
493 vec![Filter::new().kind(Kind::TEXT_NOTE)],
494 );
495 let json = serde_json::to_string(&msg).unwrap();
496 assert!(json.starts_with("[\"REQ\",\"sub-1\","));
497 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
498 assert_eq!(parsed, msg);
499 }
500
501 #[test]
502 fn req_rejects_zero_filters() {
503 let json = "[\"REQ\",\"sub-1\"]";
504 let err = serde_json::from_str::<ClientMessage>(json).unwrap_err();
505 assert!(err.to_string().contains("at least one filter"));
506 }
507
508 #[test]
509 fn close_round_trip() {
510 let msg = ClientMessage::close(SubscriptionId::new("sub-1").unwrap());
511 let json = serde_json::to_string(&msg).unwrap();
512 assert_eq!(json, "[\"CLOSE\",\"sub-1\"]");
513 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
514 assert_eq!(parsed, msg);
515 }
516
517 #[test]
518 fn auth_round_trip() {
519 let msg = ClientMessage::auth(signed_event());
520 let json = serde_json::to_string(&msg).unwrap();
521 assert!(json.starts_with("[\"AUTH\","));
522 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
523 assert_eq!(parsed, msg);
524 }
525
526 #[test]
527 fn count_round_trip() {
528 let msg = ClientMessage::count(
529 SubscriptionId::new("sub-1").unwrap(),
530 Filter::new().kind(Kind::TEXT_NOTE),
531 );
532 let json = serde_json::to_string(&msg).unwrap();
533 assert!(json.starts_with("[\"COUNT\",\"sub-1\","));
534 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
535 assert_eq!(parsed, msg);
536 }
537
538 #[test]
539 fn unknown_tag_rejected() {
540 let json = "[\"FOO\",\"sub-1\"]";
541 let err = serde_json::from_str::<ClientMessage>(json).unwrap_err();
542 assert!(err.to_string().contains("unknown client message tag"));
543 }
544
545 #[test]
546 fn empty_array_rejected() {
547 let json = "[]";
548 let err = serde_json::from_str::<ClientMessage>(json).unwrap_err();
549 assert!(err.to_string().contains("must not be empty"));
550 }
551
552 #[test]
553 fn neg_open_round_trip() {
554 let msg = ClientMessage::neg_open(
555 SubscriptionId::new("sync-1").unwrap(),
556 Filter::new().kind(Kind::TEXT_NOTE),
557 "0123456789abcdef".to_owned(),
558 );
559 let json = serde_json::to_string(&msg).unwrap();
560 assert!(json.starts_with("[\"NEG-OPEN\",\"sync-1\","));
561 assert!(json.ends_with(",\"0123456789abcdef\"]"));
562 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
563 assert_eq!(parsed, msg);
564 }
565
566 #[test]
567 fn neg_msg_round_trip() {
568 let msg = ClientMessage::neg_msg(
569 SubscriptionId::new("sync-1").unwrap(),
570 "deadbeef".to_owned(),
571 );
572 let json = serde_json::to_string(&msg).unwrap();
573 assert_eq!(json, "[\"NEG-MSG\",\"sync-1\",\"deadbeef\"]");
574 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
575 assert_eq!(parsed, msg);
576 }
577
578 #[test]
579 fn neg_close_round_trip() {
580 let msg = ClientMessage::neg_close(SubscriptionId::new("sync-1").unwrap());
581 let json = serde_json::to_string(&msg).unwrap();
582 assert_eq!(json, "[\"NEG-CLOSE\",\"sync-1\"]");
583 let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
584 assert_eq!(parsed, msg);
585 }
586
587 #[test]
588 fn neg_open_missing_initial_message_rejected() {
589 let json = "[\"NEG-OPEN\",\"sync-1\",{}]";
593 let err = serde_json::from_str::<ClientMessage>(json).unwrap_err();
594 assert!(err.to_string().contains("missing initial message"));
595 }
596}